1#!/usr/bin/env zsh
2# ============================================================================
3# Arch Linux Maintenance TUI
4# ============================================================================
5
6center_box() {
7 local content="$1"
8 local term_width box_width pad
9
10 term_width=$(tput cols)
11
12 # Get the width of the content (rough estimate)
13 box_width=$(echo "$content" | wc -L)
14
15 pad=$(( (term_width - box_width) / 2 ))
16 (( pad < 0 )) && pad=0
17
18 # Add left padding to each line of the box
19 echo "$content" | while IFS= read -r line; do
20 printf "%*s%s\n" "$pad" "" "$line"
21 done
22}
23
24cleanup() {
25 echo ""
26 tput cnorm 2>/dev/null || true
27 show_header
28 gum style --foreground "$MOCHA_RED" --bold "Operation cancelled by user."
29 sub_text "Press any key to exit..."
30 read -n 1
31 exit 130
32}
33
34gum_safe() {
35 local resultCode
36 gum "$@"
37 resultCode=$?
38
39 if (( resultCode == 130 )); then
40 cleanup
41 elif (( resultCode != 0 )); then
42 return $resultCode
43 fi
44}
45
46
47# -------------------------------------
48
49export MOCHA_MAUVE="#cba6f7"
50export MOCHA_LAVENDER="#b4befe"
51export MOCHA_GREEN="#a6e3a1"
52export MOCHA_PEACH="#f5c2e7"
53export MOCHA_RED="#f38ba8"
54export MOCHA_TEXT="#cdd6f4"
55export MOCHA_OVERLAY="#6c7086"
56export MOCHA_BASE="#1e1e2e"
57
58# -- Gum Defaults --
59export GUM_SPIN_SPINNER="dot"
60export GUM_SPIN_SPINNER_FOREGROUND="$MOCHA_LAVENDER"
61export GUM_SPIN_TITLE_FOREGROUND="$MOCHA_TEXT"
62export GUM_CONFIRM_PROMPT_FOREGROUND="$MOCHA_MAUVE"
63export GUM_CONFIRM_SELECTED_BACKGROUND="$MOCHA_MAUVE"
64export GUM_CONFIRM_SELECTED_FOREGROUND="$MOCHA_BASE"
65export GUM_CONFIRM_UNSELECTED_FOREGROUND="$MOCHA_OVERLAY"
66export GUM_STYLE_BORDER="double"
67export GUM_STYLE_BORDER_FOREGROUND="$MOCHA_MAUVE"
68export GUM_STYLE_FOREGROUND="$MOCHA_TEXT"
69
70# -------------------------------------
71# Dependency Checks
72# -------------------------------------
73
74LOG_DIR="$HOME/.local/var/log"
75mkdir -p "$LOG_DIR"
76LOG_FILE="$LOG_DIR/maintenance-$(date +%F_%H-%M-%S).log"
77
78AUR_HELPER="yay"
79
80if ! command -v gum &>/dev/null || ! command -v yay &>/dev/null || ! command -v paccache &>/dev/null; then
81 command -v gum &>/dev/null || { echo "❌ Error: 'gum' is not installed. Please install it to proceed."; exit 1; }
82 command -v yay &>/dev/null || { gum style --foreground "$MOCHA_RED" "❌ Error: No AUR helper (paru/yay) found."; exit 1; }
83 command -v paccache &>/dev/null || { gum style --foreground "$MOCHA_RED" "❌ Error: 'pacman-contrib' is missing." "Install it with: sudo pacman -S pacman-contrib"; exit 1; }
84fi
85
86show_header() {
87 clear
88 header=$(gum_safe style \
89 --align "center" \
90 --border "double" \
91 --margin "1" \
92 --padding "1 2" \
93 --border-foreground "$MOCHA_MAUVE" \
94 "☕ Arch Linux Spring-Clean"
95 )
96 center_box "$header"
97}
98
99run_task() {
100 local label="$1"
101 local cmd="$2"
102
103 if gum_safe spin --title "$label" -- sh -c "$cmd >> $LOG_FILE 2>&1"; then
104 gum_safe style --foreground "$MOCHA_GREEN" --border none "✓ $label completed"
105 else
106 gum_safe style --foreground "$MOCHA_RED" --border none "✗ $label failed! Check logs."
107 fi
108}
109
110section_title() {
111 echo ""
112 gum_safe style --foreground "$MOCHA_MAUVE" --bold --border none "$1"
113}
114
115sub_text() {
116 gum_safe style --foreground "$MOCHA_OVERLAY" --border none "$1"
117}
118
119# -------------------------------------
120# Execution
121# -------------------------------------
122
123show_header
124
125# get sudo upfront
126if ! sudo -v; then
127 gum_safe style --foreground "$MOCHA_RED" "❌ Error: Sudo authentication failed."
128 exit 1
129fi
130
131# --- Step 1 ---
132section_title "1. System Upgrade"
133if gum_safe confirm "Upgrade system packages?"; then
134 $AUR_HELPER -Syu
135else
136 sub_text "Skipped upgrade."
137fi
138
139# --- Step 2 ---
140section_title "2. Pacman Cache"
141current_pkg_cache=$(du -sh /var/cache/pacman/pkg 2>/dev/null | cut -f1)
142
143if gum_safe confirm "Clean pacman cache? (Current: $current_pkg_cache)"; then
144 run_task "Pruning package cache" "sudo paccache -vrk 2 && sudo paccache -ruk0"
145else
146 sub_text "Skipped package cache."
147fi
148
149# --- Step 3 ---
150section_title "3. Orphaned Packages"
151ORPHANS=$($AUR_HELPER -Qtdq)
152
153if [[ -z "$ORPHANS" ]]; then
154 gum_safe style --foreground "$MOCHA_GREEN" --border none "✓ No orphaned packages found."
155else
156 gum_safe style --foreground "$MOCHA_PEACH" --border none "Found orphaned packages:"
157 echo "$ORPHANS" | gum_safe format
158
159 if gum_safe confirm "Remove orphaned packages?"; then
160 ORPHAN_LIST=$(echo "$ORPHANS" | tr '\n' ' ')
161 run_task "Removing orphaned packages" "sudo pacman -Rns --noconfirm $ORPHAN_LIST"
162 else
163 sub_text "Skipped orphan removal."
164 fi
165fi
166
167# --- Step 4 ---
168section_title "4. User Cache"
169sub_text "Targets: thumbnails, yay, paru, pip, npm"
170
171LARGE_CACHES=(${(f)"$(du -d 1 -h ~/.cache 2>/dev/null | grep -E '^([5-9][0-9][0-9]M|[0-9]+(\.[0-9]+)?G)' | grep -vE "\.cache$")"})
172
173if [[ ${#LARGE_CACHES[@]} -eq 0 ]]; then
174 sub_text "Everything is clean! No folders > 500MB."
175else
176 SELECTED=$(printf "%s\n" "${LARGE_CACHES[@]}" | gum choose --no-limit --header "Select caches to wash")
177
178 if [[ -z "$SELECTED" ]]; then
179 sub_text "Skipped user cache."
180 else
181 echo "$SELECTED" | while read -r line; do
182 TARGET_PATH=$(echo "$line" | awk '{print $2}')
183 if [[ -d "$TARGET_PATH" ]]; then
184 run_task "Cleaning ${TARGET_PATH}" "rm -rf ${TARGET_PATH:?}/*"
185 fi
186 done
187 fi
188fi
189
190# --- Step 5 ---
191section_title "5. System Logs"
192current_journal=$(journalctl --disk-usage | awk '{print $NF}')
193
194if gum_safe confirm "Vacuum logs > 7d? (Current: $current_journal)"; then
195 run_task "Rotating logs" "sudo journalctl --rotate"
196 run_task "Vacuuming logs" "sudo journalctl --vacuum-time=7d"
197else
198 sub_text "Skipped logs."
199fi
200
201# --- Step 6 ---
202section_title "6. Service Health"
203failed_count=$(systemctl list-units --state=failed --no-legend | wc -l)
204
205if (( failed_count == 0 )); then
206 gum_safe style --foreground "$MOCHA_GREEN" --border none "✓ All systems operational."
207else
208 gum_safe style --foreground "$MOCHA_RED" --border none "⚠️ $failed_count services failed:"
209 systemctl --failed --no-pager --plain | gum_safe format
210fi
211
212echo ""
213gum_safe style --padding "1" "✨ Maintenance Complete."
214gum_safe style --foreground "$MOCHA_GREEN" --bold "Review the log at: $LOG_FILE"
215gum spin --spinner "moon" --title "Press any key to close..." -- bash -c 'read -n 1 -s'