Files
omarchy_setup/omarchy-setup.sh

338 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
# ===============================================================
# 🌀 Omarchy Setup Script — Configuración modular para Arch Linux
# ===============================================================
set -u
# Directorio del script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MODULES_DIR="${SCRIPT_DIR}/modules"
REPO_BASE="https://raw.githubusercontent.com/marcogll/omarchy_setup/main"
# Verificar que los módulos existen
if [[ ! -d "${MODULES_DIR}" ]] || [[ ! -f "${MODULES_DIR}/common.sh" ]]; then
echo -e "\033[0;31m✗ Error: Módulos no encontrados\033[0m"
echo ""
echo "Este script requiere que los módulos estén presentes localmente."
echo "Por favor, clona el repositorio completo:"
echo ""
echo " git clone https://github.com/marcogll/omarchy_setup.git"
echo " cd omarchy_setup"
echo " ./omarchy-setup.sh"
echo ""
exit 1
fi
# Cargar funciones comunes
source "${MODULES_DIR}/common.sh"
# Asegurar que los módulos son ejecutables (para ejecución individual)
log_info "Verificando permisos de los módulos..."
chmod +x "${MODULES_DIR}"/*.sh 2>/dev/null || true
# --- Funciones de UI Mejorada (Spinner y Barra de Progreso) ---
SPINNER_PID=
SPINNER_DEVICE_ACTIVE=
# Inicia una animación de spinner en segundo plano
# Uso: start_spinner "Mensaje..."
start_spinner() {
local message="$1"
local device="/dev/tty"
if [[ ! -w "$device" ]]; then
device="/dev/null"
fi
SPINNER_DEVICE_ACTIVE="$device"
(
local chars="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
local dev="$device"
while :; do
for (( i=0; i<${#chars}; i++ )); do
printf '\r\033[K%s %s' "${CYAN}${chars:$i:1}${NC}" "$message" >"$dev"
sleep 0.1
done
done
) &
SPINNER_PID=$!
# Ocultar cursor en la terminal, si es posible
printf '\033[?25l' >"$device" 2>/dev/null || true
}
# Detiene el spinner y muestra un mensaje de finalización
# Uso: stop_spinner $? "Mensaje de éxito" "Mensaje de error"
stop_spinner() {
local exit_code=$1
local success_msg=$2
local error_msg=${3:-"Ocurrió un error"}
local device="${SPINNER_DEVICE_ACTIVE:-/dev/tty}"
if [[ ! -w "$device" ]]; then
device="/dev/null"
fi
if [[ -n "$SPINNER_PID" ]] && kill -0 "$SPINNER_PID" 2>/dev/null; then
kill "$SPINNER_PID" &>/dev/null
wait "$SPINNER_PID" &>/dev/null
fi
# Limpiar la línea del spinner
printf '\r\033[K' >"$device"
if [[ $exit_code -eq 0 ]]; then
log_success "$success_msg"
else
log_error "$error_msg"
fi
# Restaurar cursor
printf '\033[?25h' >"$device" 2>/dev/null || true
SPINNER_PID=
SPINNER_DEVICE_ACTIVE=
}
# --- Definición de Módulos ---
# Clave: Opción del menú
# Valor: "Nombre del Fichero;Función Principal;Descripción;Tipo (bg/fg)"
# Tipo 'bg': Tareas de fondo, usan spinner.
# Tipo 'fg': Tareas interactivas (foreground), no usan spinner.
declare -A MODULES
MODULES=(
["1"]="apps;run_module_main;📦 Instalar Aplicaciones (VS Code, VLC, drivers, etc.);bg"
["2"]="zsh-config;install_zsh;🐚 Configurar Zsh (shell, plugins, config);bg"
["3"]="docker;install_docker;🐳 Instalar Docker y Portainer;fg"
["4"]="zerotier;install_zerotier;🌐 Instalar ZeroTier VPN;bg"
["5"]="printer;install_printer;🖨️ Configurar Impresoras (CUPS);bg"
["6"]="mouse_cursor;install_mouse_cursor;🖱️ Instalar Tema de Cursor (Bibata);bg"
["7"]="icon_manager;run_module_main;🎨 Gestionar Temas de Iconos (Papirus, Tela, etc.);fg"
["8"]="davinci-resolve;install_davinci_resolve;🎬 Instalar DaVinci Resolve (Intel Edition);fg"
["H"]="hyprland-config;run_module_main;🎨 Instalar Configuración de Hyprland;bg"
["F"]="disk-format;run_module_main;💾 Formatear un Disco (FAT32, exFAT, NTFS, ext4);fg"
)
# Módulos a incluir en la opción "Instalar Todo"
INSTALL_ALL_CHOICES=("1" "2" "3" "4" "5" "6" "8")
# Función para mostrar el menú
show_menu() {
clear
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}${NC} ${BOLD}🌀 Omarchy Setup Script — Configuración Modular${NC} ${CYAN}${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BOLD}Selecciona las opciones que deseas instalar:${NC}"
echo ""
# Generar menú dinámicamente
for key in "${!MODULES[@]}"; do
IFS=';' read -r _ _ description _ <<< "${MODULES[$key]}"
# Asegurarse de que las claves numéricas se ordenen correctamente
echo -e " ${GREEN}${key})${NC} ${description}"
done | sort -V
echo -e " ${GREEN}A)${NC} ✅ Instalar Todo (opciones 1, 2, 3, 4, 5, 6, 8)"
echo -e " ${GREEN}0)${NC} 🚪 Salir"
echo ""
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -ne "${BOLD}Selecciona opción: ${NC}"
}
# Función para ejecutar módulo
run_module() {
local choice=$1
IFS=';' read -r module_file func_name description type <<< "${MODULES[$choice]}"
# Para funciones internas como update_system
if [[ ! -f "${MODULES_DIR}/${module_file}.sh" && "$(type -t "$func_name")" == "function" ]]; then
"$func_name"
return $?
fi
local full_path="${MODULES_DIR}/${module_file}.sh"
if [[ ! -f "$full_path" ]]; then
log_error "Módulo para la opción '${choice}' (${module_file}.sh) no encontrado."
return 1
fi
# Exportar REPO_BASE para que los módulos lo puedan usar
export REPO_BASE
# Cargar y ejecutar el módulo
source "$full_path"
if [[ "$(type -t "$func_name")" != "function" ]]; then
log_error "La función principal '${func_name}' no está definida en '${module_file}.sh'."
return 1
fi
"$func_name"
return $?
}
# Función para ejecutar un módulo con lógica de reintento
# Intenta ejecutar un módulo. Si falla, lo reintenta una vez más.
# Devuelve 0 si tiene éxito en cualquier intento, 1 si falla en ambos.
run_module_with_retry() {
local choice=$1
local max_intentos=2
local intento_actual=1
while [ $intento_actual -le $max_intentos ]; do
run_module "$choice"
local estado_salida=$?
if [ $estado_salida -eq 0 ]; then
return 0 # Éxito, salimos de la función
fi
log_warning "El módulo falló en el intento $intento_actual (código: $estado_salida)."
if [ $intento_actual -lt $max_intentos ]; then
log_info "Reintentando en 3 segundos..."
sleep 3
fi
((intento_actual++))
done
log_error "El módulo falló después de $max_intentos intentos."
return 1 # Falla definitiva
}
# Función para instalar todo
install_all() {
log_step "Instalación Completa de Omarchy"
local failed=()
for choice in "${INSTALL_ALL_CHOICES[@]}"; do
IFS=';' read -r module_file _ description type <<< "${MODULES[$choice]}"
# Separador visual para cada módulo
echo -e "\n${MAGENTA}────────────────────────────────────────────────────────────${NC}"
log_step "Iniciando Módulo: ${description}"
# Ejecutar con spinner para tareas de fondo (bg)
if [[ "$type" == "bg" ]]; then
start_spinner "Ejecutando: ${description#* }..."
if run_module_with_retry "${choice}"; then
stop_spinner 0 "Módulo '${description}' finalizado."
else
stop_spinner 1 "Error en el módulo '${description}'."
failed+=("${module_file}")
fi
else # Ejecutar sin spinner para tareas interactivas (fg) y sin reintento
if ! run_module "${choice}"; then
log_error "Error en el módulo '${description}'."
failed+=("${module_file}")
fi
fi
echo ""
done
if [[ ${#failed[@]} -eq 0 ]]; then
log_success "Todas las instalaciones se completaron correctamente"
else
log_warning "Algunos módulos fallaron: ${failed[*]}"
fi
}
# Función principal
main() {
# Limpieza al salir: detener el spinner y restaurar el cursor
trap 'stop_spinner 1 "Script interrumpido." >/dev/null 2>&1; exit 1' INT TERM
# Limpieza final al salir normalmente
trap 'tput cnorm' EXIT
# Verificar que estamos en Arch Linux
if [[ ! -f /etc/arch-release ]]; then
log_error "Este script está diseñado para Arch Linux"
exit 1
fi
# Verificar permisos de sudo
if ! sudo -n true 2>/dev/null; then
log_info "Este script requiere permisos de sudo"
sudo -v
fi
# Mantener sudo activo en background
local parent_pid=$$
(while true; do
sudo -n true
sleep 60
kill -0 "$parent_pid" || exit
done 2>/dev/null) &
# Bucle principal del menú
# Exportar funciones para que los submódulos las puedan usar
export -f start_spinner
export -f stop_spinner
while true; do
show_menu
read -r choice
choice=$(echo "${choice// /}" | tr '[:lower:]' '[:upper:]') # Eliminar espacios y convertir a mayúsculas
if [[ -v "MODULES[$choice]" ]]; then
IFS=';' read -r _ _ description type <<< "${MODULES[$choice]}"
# Manejo especial para DaVinci Resolve
if [[ "$choice" == "8" ]]; then
log_warning "DaVinci Resolve requiere el ZIP de instalación en ~/Downloads/"
echo -ne "${BOLD}¿Continuar con la instalación? [s/N]: ${NC} "
read -r confirm
if ! [[ "${confirm}" =~ ^[SsYy]$ ]]; then
log_info "Instalación cancelada"
read -p "Presiona Enter para continuar..."
continue
fi
fi
if [[ "$type" == "bg" ]]; then
spinner_msg="${description#* }..." # "Instalar Apps..."
start_spinner "${spinner_msg}"
if run_module_with_retry "$choice"; then
stop_spinner 0 "Módulo '${description}' finalizado."
else
stop_spinner 1 "Error en el módulo '${description}'."
fi
else # 'fg'
log_info "Ejecutando módulo interactivo: ${description}"
run_module "$choice"
fi
echo ""
read -p "Presiona Enter para continuar..."
elif [[ "$choice" == "A" ]]; then
log_warning "La opción 'Instalar Todo' ejecutará los módulos: 1, 2, 3, 4, 5, 6 y 8."
log_warning "DaVinci Resolve requiere que el ZIP de instalación esté en ~/Downloads/."
echo -ne "${BOLD}¿Confirmas que deseas instalar todas las opciones ahora? [s/N]: ${NC}"
read -r confirm
if [[ "${confirm}" =~ ^[SsYy]$ ]]; then
install_all
else
log_info "Instalación cancelada"
fi
echo ""
read -p "Presiona Enter para continuar..."
elif [[ "$choice" == "0" ]]; then
log_info "Saliendo..."
exit 0
else
log_error "Opción inválida. Presiona Enter para continuar..."
read -r
fi
done
}
# Ejecutar función principal
# --- Redirección de logs ---
# Crear el directorio de logs si no existe
mkdir -p "${SCRIPT_DIR}/logs"
# Crear un nombre de archivo de log con la fecha y hora
LOG_FILE="${SCRIPT_DIR}/logs/omarchy-setup-$(date +%F_%H-%M-%S).log"
# Ejecutar la función principal y redirigir toda la salida (stdout y stderr)
# al archivo de log, mientras también se muestra en la terminal.
main "$@" 2>&1 | tee -a "${LOG_FILE}"