Files
omarchy_setup/omarchy-setup.sh
google-labs-jules[bot] c297339e9e refactor: improve maintainability of omarchy-setup.sh
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.
2025-11-18 22:42:55 +00:00

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}"