mirror of
https://github.com/marcogll/omarchy_setup.git
synced 2026-01-13 21:35:16 +00:00
Dynamically generate the "Install All" module list instead of hardcoding it. This makes the script easier to extend and less prone to error when adding or removing modules. Also, improve the user-facing text for the "Install All" option to be clearer and more accurate. Finally, remove unused code to clean up the script.
435 lines
15 KiB
Bash
Executable File
435 lines
15 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"
|
|
SUDO_PASSWORD=""
|
|
|
|
# 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 (Indicador de Progreso) ---
|
|
|
|
SPINNER_ACTIVE=0
|
|
SPINNER_MESSAGE=
|
|
|
|
pause_spinner() {
|
|
if (( SPINNER_ACTIVE )); then
|
|
SPINNER_ACTIVE=0
|
|
fi
|
|
}
|
|
|
|
resume_spinner() {
|
|
if [[ -n "$SPINNER_MESSAGE" ]]; then
|
|
log_info "$SPINNER_MESSAGE"
|
|
SPINNER_ACTIVE=1
|
|
fi
|
|
}
|
|
|
|
start_spinner() {
|
|
local message="$1"
|
|
pause_spinner
|
|
SPINNER_MESSAGE="$message"
|
|
SPINNER_ACTIVE=1
|
|
log_info "$message"
|
|
}
|
|
|
|
stop_spinner() {
|
|
local exit_code=$1
|
|
local success_msg=$2
|
|
local error_msg=${3:-"Ocurrió un error"}
|
|
|
|
pause_spinner
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
log_success "$success_msg"
|
|
else
|
|
log_error "$error_msg"
|
|
fi
|
|
|
|
SPINNER_MESSAGE=
|
|
}
|
|
|
|
ensure_sudo_session() {
|
|
if sudo -n true 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "${SUDO_PASSWORD:-}" ]]; then
|
|
if printf '%s\n' "$SUDO_PASSWORD" | sudo -S -v >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
SUDO_PASSWORD=""
|
|
log_warning "La contraseña de sudo almacenada no es válida. Se solicitará nuevamente."
|
|
fi
|
|
|
|
pause_spinner
|
|
|
|
local attempts=0
|
|
while (( attempts < 3 )); do
|
|
if (( attempts == 0 )); then
|
|
log_info "Se requiere autenticación de sudo para continuar."
|
|
else
|
|
log_info "Intenta ingresar la contraseña nuevamente."
|
|
fi
|
|
local password_input=""
|
|
read -s -p "Contraseña de sudo: " password_input
|
|
echo ""
|
|
|
|
if [[ -z "$password_input" ]]; then
|
|
log_warning "La contraseña no puede estar vacía."
|
|
elif printf '%s\n' "$password_input" | sudo -S -v >/dev/null 2>&1; then
|
|
SUDO_PASSWORD="$password_input"
|
|
log_success "Sesión de sudo autenticada."
|
|
return 0
|
|
else
|
|
log_error "Contraseña de sudo incorrecta."
|
|
fi
|
|
|
|
((attempts++))
|
|
done
|
|
|
|
log_error "No se pudo autenticar con sudo después de varios intentos."
|
|
return 1
|
|
}
|
|
|
|
# --- 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;fg"
|
|
["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"
|
|
["K"]="ssh-keyring;sync_ssh_keyring;🔐 Sincronizar claves SSH con GNOME Keyring;fg"
|
|
["F"]="disk-format;run_module_main;💾 Habilitar Formatos FAT/exFAT/NTFS/ext4;bg"
|
|
["R"]="davinci-resolve;install_davinci_resolve;🎬 Instalar DaVinci Resolve (Intel Edition);fg"
|
|
["H"]="hyprland-config;run_module_main;🎨 Instalar Configuración de Hyprland;bg"
|
|
)
|
|
|
|
# Módulos a excluir de la opción "Instalar Todo"
|
|
EXCLUDED_FROM_ALL=("R")
|
|
|
|
# Generar dinámicamente la lista de módulos para "Instalar Todo"
|
|
get_install_all_choices() {
|
|
local choices=()
|
|
for key in $(printf '%s\n' "${!MODULES[@]}" | sort -V); do
|
|
# Verificar si la clave no está en el array de exclusión
|
|
if ! [[ " ${EXCLUDED_FROM_ALL[*]} " =~ " ${key} " ]]; then
|
|
choices+=("$key")
|
|
fi
|
|
done
|
|
echo "${choices[@]}"
|
|
}
|
|
|
|
# Módulos a incluir en la opción "Instalar Todo"
|
|
INSTALL_ALL_CHOICES=($(get_install_all_choices))
|
|
|
|
# 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
|
|
|
|
local install_all_keys=$(IFS=,; echo "${INSTALL_ALL_CHOICES[*]}")
|
|
echo -e " ${GREEN}A)${NC} ✅ Instalar Todo (${install_all_keys//,/, }) (excluye DaVinci)"
|
|
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
|
|
local module_entry="${MODULES[$choice]}"
|
|
local module_type=""
|
|
if [[ -n "$module_entry" ]]; then
|
|
IFS=';' read -r _ _ _ module_type <<< "$module_entry"
|
|
fi
|
|
local tmp_output=""
|
|
if [[ "$module_type" == "bg" ]]; then
|
|
tmp_output="$(mktemp)"
|
|
fi
|
|
|
|
while [ $intento_actual -le $max_intentos ]; do
|
|
local estado_salida=0
|
|
if [[ -n "$tmp_output" ]]; then
|
|
run_module "$choice" >"$tmp_output" 2>&1
|
|
estado_salida=$?
|
|
if [[ -s "$tmp_output" ]]; then
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
cat "$tmp_output"
|
|
fi
|
|
: > "$tmp_output"
|
|
if [[ $estado_salida -ne 0 && $intento_actual -lt $max_intentos ]] && declare -F resume_spinner >/dev/null; then
|
|
resume_spinner
|
|
fi
|
|
else
|
|
run_module "$choice"
|
|
estado_salida=$?
|
|
fi
|
|
|
|
if [ $estado_salida -eq 0 ]; then
|
|
if [[ -n "$tmp_output" ]]; then
|
|
rm -f "$tmp_output"
|
|
fi
|
|
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."
|
|
if [[ -n "$tmp_output" ]]; then
|
|
rm -f "$tmp_output"
|
|
fi
|
|
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]}"
|
|
|
|
if ! ensure_sudo_session; then
|
|
failed+=("${module_file}")
|
|
continue
|
|
fi
|
|
|
|
# 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; unset SUDO_PASSWORD; exit 1' INT TERM
|
|
# Limpieza final al salir normalmente
|
|
trap 'tput cnorm; unset SUDO_PASSWORD' 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 ! ensure_sudo_session; then
|
|
log_error "No se pudieron obtener privilegios de sudo. Saliendo."
|
|
exit 1
|
|
fi
|
|
|
|
# Mantener sudo activo en background
|
|
local parent_pid=$$
|
|
(while true; do
|
|
sudo -n true >/dev/null 2>&1
|
|
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
|
|
export -f pause_spinner
|
|
export -f resume_spinner
|
|
export -f ensure_sudo_session
|
|
|
|
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" == "R" ]]; 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 ! ensure_sudo_session; then
|
|
read -p "Presiona Enter para continuar..."
|
|
continue
|
|
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 ""
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
read -p "Presiona Enter para continuar..."
|
|
|
|
elif [[ "$choice" == "A" ]]; then
|
|
local modules_to_install=$(IFS=,; echo "${INSTALL_ALL_CHOICES[*]}")
|
|
log_warning "La opción 'Instalar Todo' ejecutará los módulos: ${modules_to_install//,/, }."
|
|
log_info "Los módulos excluidos (como DaVinci Resolve) deben instalarse por separado."
|
|
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 ""
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
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..."
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
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}"
|