557 lines
19 KiB
Bash
557 lines
19 KiB
Bash
#!/bin/bash
|
|
|
|
export SHELL=$(command -v bash)
|
|
|
|
# ==============================================================================
|
|
# Shorin Arch Setup - Main Installer (v1.2)
|
|
# ==============================================================================
|
|
|
|
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
SCRIPTS_DIR="$BASE_DIR/scripts"
|
|
STATE_FILE="$BASE_DIR/.install_progress"
|
|
|
|
# --- Source Visual Engine ---
|
|
if [ -f "$SCRIPTS_DIR/00-utils.sh" ]; then
|
|
source "$SCRIPTS_DIR/00-utils.sh"
|
|
else
|
|
echo "Error: 00-utils.sh not found."
|
|
exit 1
|
|
fi
|
|
|
|
# --- Global Cleanup on Exit ---
|
|
cleanup() {
|
|
rm -f "/tmp/shorin_install_user"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# --- Global Trap (Restore Cursor on Exit) ---
|
|
cleanup_on_exit() {
|
|
tput cnorm
|
|
}
|
|
trap cleanup_on_exit EXIT
|
|
|
|
# --- Environment ---
|
|
export DEBUG=${DEBUG:-0}
|
|
export CN_MIRROR=${CN_MIRROR:-0}
|
|
|
|
check_root
|
|
chmod +x "$SCRIPTS_DIR"/*.sh
|
|
|
|
# --- ASCII Banners ---
|
|
banner1() {
|
|
cat << "EOF"
|
|
██████ ██ ██ ██████ ███████ ██ ███ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
███████ ███████ ██ ██ ██████ ██ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
██████ ██ ██ ██████ ██ ██ ██ ██ ████
|
|
EOF
|
|
}
|
|
|
|
export SHORIN_BANNER_IDX=0
|
|
|
|
show_banner() {
|
|
clear
|
|
echo -e "${H_CYAN}"
|
|
case $SHORIN_BANNER_IDX in
|
|
0) banner1 ;;
|
|
esac
|
|
echo -e "${NC}"
|
|
echo -e "${DIM} :: Arch Linux Automation ::${NC}"
|
|
echo -e ""
|
|
}
|
|
|
|
# --- Desktop Selection Menu (FZF Powered) ---
|
|
select_desktop() {
|
|
if ! command -v fzf &> /dev/null; then
|
|
echo -e " ${DIM}Installing fzf for interactive menu...${NC}"
|
|
pacman -Sy --noconfirm --needed fzf >/dev/null 2>&1
|
|
fi
|
|
|
|
local MENU_ITEMS=(
|
|
"No_Desktop|none"
|
|
"Surprise Me!|random"
|
|
""
|
|
"KDE_Plasma ${H_YELLOW}(Recommended)${NC}|kde"
|
|
""
|
|
"Shorin_DMS_Niri ${H_YELLOW}(Recommended)${NC}|shorindmsgit"
|
|
"Shorin_Noctalia_Niri|shorinnocniri"
|
|
"Shorin_Niri|shorinniri"
|
|
"Minimal_Niri|minimalniri"
|
|
"Shorin_DMS_Hyprland_Scrolling|hyprniri"
|
|
""
|
|
"GNOME |gnome"
|
|
""
|
|
"Quickshell: End4--illogical_impulse|end4"
|
|
"Quickshell: DMS--DankMaterialShell|dms"
|
|
"Quickshell: Caelestia|caelestia"
|
|
)
|
|
|
|
while true; do
|
|
show_banner
|
|
|
|
local fzf_list=()
|
|
local idx=1
|
|
for item in "${MENU_ITEMS[@]}"; do
|
|
[[ -z "$item" ]] && continue
|
|
|
|
local name="${item%%|*}"
|
|
local val="${item##*|}"
|
|
local colored_idx="${H_CYAN}[${idx}]${NC}"
|
|
|
|
if [ $idx -lt 10 ]; then
|
|
fzf_list+=("${colored_idx} ${name}\t${val}\t${name}")
|
|
else
|
|
fzf_list+=("${colored_idx} ${name}\t${val}\t${name}")
|
|
fi
|
|
((idx++))
|
|
done
|
|
|
|
local selected
|
|
selected=$(printf "%b\n" "${fzf_list[@]}" | sed '/^[[:space:]]*$/d' | fzf \
|
|
--ansi \
|
|
--delimiter='\t' \
|
|
--with-nth=1 \
|
|
--info=hidden \
|
|
--layout=reverse \
|
|
--border="rounded" \
|
|
--border-label=" Select Desktop Environment " \
|
|
--border-label-pos=5 \
|
|
--color="marker:cyan,pointer:cyan,label:yellow" \
|
|
--header=" [J/K] Select | [Enter] confirm" \
|
|
--pointer=">" \
|
|
--bind 'j:down,k:up,ctrl-c:abort,esc:abort' \
|
|
--height=~20)
|
|
|
|
local fzf_status=$?
|
|
|
|
if [ $fzf_status -eq 130 ]; then
|
|
echo -e "\n ${H_RED}>>> Installation aborted by user.${NC}"
|
|
exit 130
|
|
fi
|
|
|
|
if [ -z "$selected" ]; then continue; fi
|
|
|
|
export DESKTOP_ENV="$(echo "$selected" | awk -F'\t' '{print $2}')"
|
|
local selected_name="$(echo "$selected" | awk -F'\t' '{print $3}')"
|
|
|
|
if [ "$DESKTOP_ENV" == "random" ]; then
|
|
local POOL=()
|
|
for item in "${MENU_ITEMS[@]}"; do
|
|
[[ -z "$item" ]] && continue
|
|
local oid="${item##*|}"
|
|
if [[ "$oid" != "none" && "$oid" != "random" ]]; then
|
|
POOL+=("$item")
|
|
fi
|
|
done
|
|
|
|
local rand_idx=$(( RANDOM % ${#POOL[@]} ))
|
|
local final_item="${POOL[$rand_idx]}"
|
|
local final_name="${final_item%%|*}"
|
|
export DESKTOP_ENV="${final_item##*|}"
|
|
|
|
echo -e "\n ${H_CYAN}>>> Randomly selected:${NC} ${BOLD}${final_name}${NC}"
|
|
read -p "$(echo -e " ${H_YELLOW}Continue with this selection? [Y/n]: ${NC}")" confirm
|
|
|
|
if [[ "${confirm,,}" == "n" ]]; then continue; else break; fi
|
|
else
|
|
log "Selected: ${selected_name}"
|
|
sleep 0.5
|
|
break
|
|
fi
|
|
done
|
|
}
|
|
|
|
# --- Optional Modules Selection Menu (FZF Powered) ---
|
|
select_optional_modules() {
|
|
local OPTIONAL_MENU=(
|
|
"Windows Linux Dualboot Setup|02a-dualboot-fix.sh"
|
|
"GPU Drivers|03b-gpu-driver.sh"
|
|
"Grub Themes|07-grub-theme.sh"
|
|
"Common Apps|99-apps.sh"
|
|
)
|
|
|
|
show_banner
|
|
|
|
local fzf_list=()
|
|
for item in "${OPTIONAL_MENU[@]}"; do
|
|
local name="${item%%|*}"
|
|
local val="${item##*|}"
|
|
fzf_list+=(" ${name}\t${val}")
|
|
done
|
|
|
|
# 核心修复:引入 --expect=ctrl-x,enter 来拦截按键动作
|
|
local selected_raw
|
|
selected_raw=$(printf "%b\n" "${fzf_list[@]}" | fzf \
|
|
--multi \
|
|
--delimiter='\t' \
|
|
--with-nth=1 \
|
|
--layout=reverse \
|
|
--border="rounded" \
|
|
--border-label=" Select Optional Modules " \
|
|
--border-label-pos=5 \
|
|
--color="marker:cyan,pointer:cyan,label:yellow" \
|
|
--header=" [TAB]: Toggle | [CTRL-X]: Skip All | [ENTER]: Confirm " \
|
|
--pointer=">" \
|
|
--expect=ctrl-x,enter \
|
|
--bind 'start:select-all,ctrl-a:select-all,ctrl-d:deselect-all,ctrl-c:abort,esc:abort,j:down,k:up' \
|
|
--height=~20)
|
|
|
|
local fzf_status=$?
|
|
if [ $fzf_status -eq 130 ]; then
|
|
echo -e "\n ${H_RED}>>> Installation aborted by user.${NC}"
|
|
exit 130
|
|
fi
|
|
|
|
OPTIONAL_MODULES=()
|
|
|
|
if [ -n "$selected_raw" ]; then
|
|
# 解析 FZF 输出:第一行是按下的键,后面是选中的内容
|
|
local key
|
|
key=$(head -n 1 <<< "$selected_raw")
|
|
local selected_items
|
|
selected_items=$(sed '1d' <<< "$selected_raw")
|
|
|
|
# 完美解决“回车默认选中光标项”的问题:用户直接按 Ctrl-X 即可退出并清空
|
|
if [[ "$key" == "ctrl-x" ]]; then
|
|
log "Skipping all optional modules..."
|
|
sleep 0.5
|
|
else
|
|
if [ -n "$selected_items" ]; then
|
|
# 利用 awk 过滤掉空行,防止产生空元素
|
|
mapfile -t OPTIONAL_MODULES < <(echo "$selected_items" | awk -F'\t' '{if ($2 != "") print $2}')
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
sys_dashboard() {
|
|
echo -e "${H_BLUE}╔════ SYSTEM DIAGNOSTICS ══════════════════════════════╗${NC}"
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Kernel${NC} : $(uname -r)"
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}User${NC} : $(whoami)"
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Desktop${NC} : ${H_CYAN}${DESKTOP_ENV^^}${NC}"
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Modules${NC} : ${#OPTIONAL_MODULES[@]} optional module(s) selected"
|
|
|
|
if [ "$CN_MIRROR" == "1" ]; then
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Network${NC} : ${H_YELLOW}CN Optimized (Manual)${NC}"
|
|
elif [ "$DEBUG" == "1" ]; then
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Network${NC} : ${H_RED}DEBUG FORCE (CN Mode)${NC}"
|
|
else
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Network${NC} : Global Default"
|
|
fi
|
|
|
|
if [ -f "$STATE_FILE" ]; then
|
|
done_count=$(wc -l < "$STATE_FILE")
|
|
echo -e "${H_BLUE}║${NC} ${BOLD}Progress${NC} : Resuming ($done_count steps recorded)"
|
|
fi
|
|
echo -e "${H_BLUE}╚══════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# --- Main Execution ---
|
|
|
|
select_desktop
|
|
select_optional_modules
|
|
clear
|
|
show_banner
|
|
sys_dashboard
|
|
|
|
MANDATORY_MODULES=(
|
|
"00-btrfs-init.sh"
|
|
"01-base.sh"
|
|
"02-musthave.sh"
|
|
"03-user.sh"
|
|
"03c-snapshot-before-desktop.sh"
|
|
"05-verify-desktop.sh"
|
|
)
|
|
|
|
ALL_MODULES=("${MANDATORY_MODULES[@]}" "${OPTIONAL_MODULES[@]}")
|
|
|
|
case "$DESKTOP_ENV" in
|
|
shorinniri) ALL_MODULES+=("04-niri-setup.sh") ;;
|
|
minimalniri) ALL_MODULES+=("04j-minimal-niri.sh") ;;
|
|
kde) ALL_MODULES+=("04b-kdeplasma-setup.sh") ;;
|
|
end4) ALL_MODULES+=("04e-illogical-impulse-end4-quickshell.sh") ;;
|
|
dms) ALL_MODULES+=("04c-dms-quickshell.sh") ;;
|
|
shorindmsgit) ALL_MODULES+=("04h-shorindms-quickshell.sh"); export SHORIN_DMS_GIT=1 ;;
|
|
hyprniri) ALL_MODULES+=("04i-shorin-hyprniri-quickshell.sh") ;;
|
|
shorinnocniri) ALL_MODULES+=("04k-shorin-noctalia-quickshell.sh") ;;
|
|
caelestia) ALL_MODULES+=("04g-caelestia-quickshell.sh") ;;
|
|
gnome) ALL_MODULES+=("04d-gnome.sh") ;;
|
|
none) log "Skipping Desktop Environment installation." ;;
|
|
*) warn "Unknown selection, skipping desktop setup." ;;
|
|
esac
|
|
|
|
mapfile -t MODULES < <(printf "%s\n" "${ALL_MODULES[@]}" | sort -u)
|
|
|
|
if [ ! -f "$STATE_FILE" ]; then touch "$STATE_FILE"; fi
|
|
|
|
TOTAL_STEPS=${#MODULES[@]}
|
|
CURRENT_STEP=0
|
|
|
|
log "Initializing installer sequence..."
|
|
sleep 0.5
|
|
|
|
# --- Reflector Mirror Update (State Aware) ---
|
|
section "Pre-Flight" "Mirrorlist Optimization"
|
|
|
|
if grep -q "^REFLECTOR_DONE$" "$STATE_FILE"; then
|
|
echo -e " ${H_GREEN}✔${NC} Mirrorlist previously optimized."
|
|
echo -e " ${DIM} Skipping Reflector steps (Resume Mode)...${NC}"
|
|
else
|
|
log "Checking Reflector..."
|
|
exe pacman -S --noconfirm --needed reflector
|
|
|
|
CURRENT_TZ=$(readlink -f /etc/localtime)
|
|
REFLECTOR_ARGS="--protocol https -a 12 -f 10 --sort rate --save /etc/pacman.d/mirrorlist --verbose"
|
|
|
|
if [[ "$CURRENT_TZ" == *"Shanghai"* ]]; then
|
|
echo ""
|
|
echo -e "${H_YELLOW}╔══════════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${H_YELLOW}║ DETECTED TIMEZONE: Asia/Shanghai ║${NC}"
|
|
echo -e "${H_YELLOW}║ Refreshing mirrors in China can be slow. ║${NC}"
|
|
echo -e "${H_YELLOW}║ Do you want to force refresh mirrors with Reflector? ║${NC}"
|
|
echo -e "${H_YELLOW}╚══════════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
read -t 60 -p "$(echo -e " ${H_CYAN}Run Reflector?[y/N] (Default No in 60s): ${NC}")" choice
|
|
if [ $? -ne 0 ]; then echo ""; fi
|
|
choice=${choice:-N}
|
|
|
|
if [[ "$choice" =~ ^[Yy]$ ]]; then
|
|
log "Running Reflector for China..."
|
|
if exe reflector $REFLECTOR_ARGS -c China; then
|
|
success "Mirrors updated."
|
|
else
|
|
warn "Reflector failed. Continuing with existing mirrors."
|
|
fi
|
|
else
|
|
log "Skipping mirror refresh."
|
|
fi
|
|
else
|
|
log "Detecting location for optimization..."
|
|
COUNTRY_CODE=$(curl -s --max-time 2 https://ipinfo.io/country)
|
|
|
|
if [ -n "$COUNTRY_CODE" ]; then
|
|
info_kv "Country" "$COUNTRY_CODE" "(Auto-detected)"
|
|
log "Running Reflector for $COUNTRY_CODE..."
|
|
if ! exe reflector $REFLECTOR_ARGS -c "$COUNTRY_CODE"; then
|
|
warn "Country specific refresh failed. Trying global speed test..."
|
|
exe reflector $REFLECTOR_ARGS
|
|
fi
|
|
else
|
|
warn "Could not detect country. Running global speed test..."
|
|
exe reflector $REFLECTOR_ARGS --latest 25
|
|
fi
|
|
success "Mirrorlist optimized."
|
|
fi
|
|
|
|
echo "REFLECTOR_DONE" >> "$STATE_FILE"
|
|
fi
|
|
|
|
# ---- update keyring-----
|
|
section "Pre-Flight" "Update Keyring"
|
|
|
|
exe pacman -Sy
|
|
exe pacman -S --noconfirm archlinux-keyring
|
|
|
|
# --- Global Update ---
|
|
section "Pre-Flight" "System update"
|
|
log "Ensuring system is up-to-date..."
|
|
|
|
if exe pacman -Syu --noconfirm; then
|
|
success "System Updated."
|
|
else
|
|
error "System update failed. Check your network."
|
|
exit 1
|
|
fi
|
|
|
|
# --- Module Loop ---
|
|
for module in "${MODULES[@]}"; do
|
|
[[ -z "$module" ]] && continue
|
|
|
|
CURRENT_STEP=$((CURRENT_STEP + 1))
|
|
script_path="$SCRIPTS_DIR/$module"
|
|
|
|
if [ ! -f "$script_path" ]; then
|
|
error "Module not found: $module"
|
|
continue
|
|
fi
|
|
|
|
if grep -q "^${module}$" "$STATE_FILE"; then
|
|
echo -e " ${H_GREEN}✔${NC} Module ${BOLD}${module}${NC} already completed."
|
|
echo -e " ${DIM} Skipping... (Delete .install_progress to force run)${NC}"
|
|
continue
|
|
fi
|
|
|
|
section "Module ${CURRENT_STEP}/${TOTAL_STEPS}" "$module"
|
|
|
|
bash "$script_path"
|
|
exit_code=$?
|
|
|
|
if [ $exit_code -eq 0 ]; then
|
|
echo "$module" >> "$STATE_FILE"
|
|
success "Module $module completed."
|
|
elif [ $exit_code -eq 130 ]; then
|
|
echo ""
|
|
warn "Script interrupted by user (Ctrl+C)."
|
|
log "Exiting without rollback. You can resume later."
|
|
exit 130
|
|
else
|
|
write_log "FATAL" "Module $module failed with exit code $exit_code"
|
|
error "Module execution failed."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Final Cleanup
|
|
# ------------------------------------------------------------------------------
|
|
section "Completion" "System Cleanup"
|
|
|
|
clean_intermediate_snapshots() {
|
|
local config_name="$1"
|
|
local start_marker="Before Shorin Setup"
|
|
|
|
local KEEP_MARKERS=(
|
|
"Before Desktop Environments"
|
|
"Before Niri Setup"
|
|
)
|
|
|
|
if ! snapper -c "$config_name" list &>/dev/null; then
|
|
return
|
|
fi
|
|
|
|
log "Scanning junk snapshots in: $config_name..."
|
|
|
|
local start_id
|
|
start_id=$(snapper -c "$config_name" list --columns number,description | grep -F "$start_marker" | awk '{print $1}' | tail -n 1)
|
|
|
|
if [ -z "$start_id" ]; then
|
|
warn "Marker '$start_marker' not found in '$config_name'. Skipping cleanup."
|
|
return
|
|
fi
|
|
|
|
local IDS_TO_KEEP=()
|
|
for marker in "${KEEP_MARKERS[@]}"; do
|
|
local found_id
|
|
found_id=$(snapper -c "$config_name" list --columns number,description | grep -F "$marker" | awk '{print $1}' | tail -n 1)
|
|
|
|
if [ -n "$found_id" ]; then
|
|
IDS_TO_KEEP+=("$found_id")
|
|
log "Found protected snapshot: '$marker' (ID: $found_id)"
|
|
fi
|
|
done
|
|
|
|
local snapshots_to_delete=()
|
|
|
|
while IFS= read -r line; do
|
|
local id
|
|
local type
|
|
|
|
id=$(echo "$line" | awk '{print $1}')
|
|
type=$(echo "$line" | awk '{print $3}')
|
|
|
|
if [[ "$id" =~ ^[0-9]+$ ]]; then
|
|
if [ "$id" -gt "$start_id" ]; then
|
|
|
|
local skip=false
|
|
for keep in "${IDS_TO_KEEP[@]}"; do
|
|
if [[ "$id" == "$keep" ]]; then
|
|
skip=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$skip" = true ]; then
|
|
continue
|
|
fi
|
|
|
|
if [[ "$type" == "pre" || "$type" == "post" ]]; then
|
|
snapshots_to_delete+=("$id")
|
|
fi
|
|
fi
|
|
fi
|
|
done < <(snapper -c "$config_name" list --columns number,type)
|
|
|
|
if [ ${#snapshots_to_delete[@]} -gt 0 ]; then
|
|
log "Deleting ${#snapshots_to_delete[@]} junk snapshots in '$config_name'..."
|
|
if exe snapper -c "$config_name" delete "${snapshots_to_delete[@]}"; then
|
|
success "Cleaned $config_name."
|
|
fi
|
|
else
|
|
log "No junk snapshots found in '$config_name'."
|
|
fi
|
|
}
|
|
|
|
log "Cleaning Pacman/Yay cache..."
|
|
exe pacman -Sc --noconfirm
|
|
|
|
clean_intermediate_snapshots "root"
|
|
clean_intermediate_snapshots "home"
|
|
|
|
DETECTED_USER=$(awk -F: '$3 == 1000 {print $1}' /etc/passwd)
|
|
TARGET_USER="${DETECTED_USER:-$(read -p "Target user: " u && echo $u)}"
|
|
HOME_DIR="/home/$TARGET_USER"
|
|
|
|
for dir in /var/cache/pacman/pkg/download-*/; do
|
|
if [ -d "$dir" ]; then
|
|
echo "Found residual directory: $dir, cleaning up..."
|
|
rm -rf "$dir"
|
|
fi
|
|
done
|
|
|
|
VERIFY_LIST="/tmp/shorin_install_verify.list"
|
|
rm -f "$VERIFY_LIST"
|
|
|
|
log "Regenerating final GRUB configuration..."
|
|
exe env LANG=en_US.UTF-8 grub-mkconfig -o /boot/grub/grub.cfg
|
|
|
|
# --- Completion ---
|
|
clear
|
|
show_banner
|
|
echo -e "${H_GREEN}╔══════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${H_GREEN}║ INSTALLATION COMPLETE ║${NC}"
|
|
echo -e "${H_GREEN}╚══════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
if [ -f "$STATE_FILE" ]; then rm "$STATE_FILE"; fi
|
|
|
|
log "Archiving log..."
|
|
if [ -f "/tmp/shorin_install_user" ]; then
|
|
FINAL_USER=$(cat /tmp/shorin_install_user)
|
|
else
|
|
FINAL_USER=$(awk -F: '$3 == 1000 {print $1}' /etc/passwd)
|
|
fi
|
|
|
|
if [ -n "$FINAL_USER" ]; then
|
|
FINAL_DOCS="/home/$FINAL_USER/Documents"
|
|
mkdir -p "$FINAL_DOCS"
|
|
if [ -f "${TEMP_LOG_FILE:-/tmp/shorin.log}" ]; then
|
|
cp "${TEMP_LOG_FILE:-/tmp/shorin.log}" "$FINAL_DOCS/log-shorin-arch-setup.txt"
|
|
chown -R "$FINAL_USER:$FINAL_USER" "$FINAL_DOCS"
|
|
echo -e " ${H_BLUE}●${NC} Log Saved : ${BOLD}$FINAL_DOCS/log-shorin-arch-setup.txt${NC}"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${H_YELLOW}>>> System requires a REBOOT.${NC}"
|
|
|
|
while read -r -t 0; do read -r; done
|
|
|
|
for i in {10..1}; do
|
|
echo -ne "\r ${DIM}Auto-rebooting in ${i}s... (Press 'n' to cancel)${NC}"
|
|
|
|
read -t 1 -n 1 input
|
|
if [ $? -eq 0 ]; then
|
|
if [[ "$input" == "n" || "$input" == "N" ]]; then
|
|
echo -e "\n\n ${H_BLUE}>>> Reboot cancelled.${NC}"
|
|
exit 0
|
|
else
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo -e "\n\n ${H_GREEN}>>> Rebooting...${NC}"
|
|
systemctl reboot |