Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- File: /usr/local/bin/yar
- USAGE: $ yar <-- Update the mirrors list.
- #!/usr/bin/env python3
- # Installation:
- # $ sudo cp ./yar /usr/local/bin/
- # $ sudo chmod +x /usr/local/bin/yar
- # ----- Colors -----
- BLACK = '\033[30m'
- RED = '\033[31m'
- GREEN = '\033[32m'
- YELLOW = '\033[33m'
- BLUE = '\033[34m'
- MAGENTA = '\033[35m'
- CYAN = '\033[36m'
- WHITE = '\033[37m'
- BRIGHT_BLACK = '\033[90m'
- BRIGHT_RED = '\033[91m'
- BRIGHT_GREEN = '\033[92m'
- BRIGHT_YELLOW = '\033[93m'
- BRIGHT_BLUE = '\033[94m'
- BRIGHT_MAGENTA = '\033[95m'
- BRIGHT_CYAN = '\033[96m'
- BRIGHT_WHITE = '\033[97m'
- # ----- Styles -----
- RESET = '\033[0m' # Reset all styles.
- BOLD = '\033[1m'
- HALF_BRIGHT = '\033[2m'
- ITALIC = '\033[3m'
- UNDERLINE = '\033[4m'
- REVERSED = '\033[7m' # Reverse foreground and background colors.
- import math
- import requests
- import shutil
- import subprocess
- import sys
- MIRRORLIST_PATH = '/etc/pacman.d/mirrorlist'
- EARTH_RADIUS_KM = 6371.0
- # Reference point: New York City
- USER_LAT = 40.730610
- USER_LON = -73.935242
- # Generated country coordinates (lat, lon).
- COUNTRY_COORDS = {
- 'Armenia': (40.0, 45.0),
- 'Australia': (-27.0, 133.0),
- 'Austria': (47.33333333, 13.33333333),
- 'Azerbaijan': (40.5, 47.5),
- 'Bangladesh': (24.0, 90.0),
- 'Belarus': (53.0, 28.0),
- 'Belgium': (50.83333333, 4.0),
- 'Brazil': (-10.0, -55.0),
- 'Bulgaria': (43.0, 25.0),
- 'Cambodia': (13.0, 105.0),
- 'Canada': (60.0, -95.0),
- 'Chile': (-30.0, -71.0),
- 'China': (35.0, 105.0),
- 'Colombia': (4.0, -72.0),
- 'Croatia': (45.16666666, 15.5),
- 'Czechia': (49.75, 15.5),
- 'Denmark': (56.0, 10.0),
- 'Ecuador': (-2.0, -77.5),
- 'Estonia': (59.0, 26.0),
- 'Finland': (64.0, 26.0),
- 'France': (46.0, 2.0),
- 'Georgia': (42.0, 43.5),
- 'Germany': (51.0, 9.0),
- 'Greece': (39.0, 22.0),
- 'Hong Kong': (22.267, 114.188),
- 'Hungary': (47.0, 20.0),
- 'Iceland': (65.0, -18.0),
- 'India': (20.0, 77.0),
- 'Indonesia': (-5.0, 120.0),
- 'Iran': (32.0, 53.0),
- 'Israel': (31.47, 35.13),
- 'Italy': (42.83333333, 12.83333333),
- 'Japan': (36.0, 138.0),
- 'Kazakhstan': (48.0196, 66.9237),
- 'Kenya': (1.0, 38.0),
- 'Latvia': (57.0, 25.0),
- 'Lithuania': (56.0, 24.0),
- 'Luxembourg': (49.75, 6.16666666),
- 'Mauritius': (-20.28333333, 57.55),
- 'Mexico': (23.0, -102.0),
- 'Moldova': (47.0, 29.0),
- 'Morocco': (32.0, -5.0),
- 'Nepal': (28.0, 84.0),
- 'Netherlands': (52.5, 5.75),
- 'New Caledonia': (-21.5, 165.5),
- 'New Zealand': (-41.0, 174.0),
- 'North Macedonia': (41.83333333, 22.0),
- 'Norway': (62.0, 10.0),
- 'Paraguay': (-23.0, -58.0),
- 'Poland': (52.0, 20.0),
- 'Portugal': (39.5, -8.0),
- 'Romania': (46.0, 25.0),
- 'Russia': (60.0, 100.0),
- 'Réunion': (-21.15, 55.5),
- 'Saudi Arabia': (25.0, 45.0),
- 'Serbia': (44.0, 21.0),
- 'Singapore': (1.36666666, 103.8),
- 'Slovakia': (48.66666666, 19.5),
- 'Slovenia': (46.11666666, 14.81666666),
- 'South Africa': (-29.0, 24.0),
- 'South Korea': (37.0, 127.5),
- 'Spain': (40.0, -4.0),
- 'Sweden': (62.0, 15.0),
- 'Switzerland': (47.0, 8.0),
- 'Taiwan': (23.5, 121.0),
- 'Thailand': (15.0, 100.0),
- 'Türkiye': (39.0, 35.0),
- 'Ukraine': (49.0, 32.0),
- 'United Arab Emirates': (24.0, 54.0),
- 'United Kingdom': (54.0, -2.0),
- 'United States': (38.0, -97.0),
- 'Uzbekistan': (41.0, 64.0),
- 'Vietnam': (16.16666666, 107.83333333),
- }
- # Calculate the great-circle distance between two points on a sphere using the Haversine formula.
- def haversine_distance(lat1, lon1, lat2, lon2, radius):
- # Convert latitude and longitude from degrees to radians.
- lat1 = math.radians(lat1)
- lon1 = math.radians(lon1)
- lat2 = math.radians(lat2)
- lon2 = math.radians(lon2)
- # Calculate the differences.
- dlat = lat2 - lat1
- dlon = lon2 - lon1
- # Haversine formula.
- # SOURCE: https://www.movable-type.co.uk/scripts/latlong.html
- # The square of half the chord length between the points.
- a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
- # The angular distance in radians.
- c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
- d = radius * c
- return d
- def generate_sorted_countries_list(lat, lon, max_countries=5):
- # Generate a list of nearest countries.
- distances = []
- for country, (country_lat, country_lon) in COUNTRY_COORDS.items():
- dist = haversine_distance(lat, lon, country_lat, country_lon, EARTH_RADIUS_KM)
- distances.append((dist, country))
- distances.sort(key=lambda x: x[0])
- nearest = [country for _, country in distances[:max_countries]]
- return ','.join(nearest)
- def update_arch_mirrors():
- # Run reflector with closest countries.
- if shutil.which('reflector') is None:
- print(f'{BRIGHT_RED}::{RESET} Reflector could not be found. Install with:')
- print(' sudo pacman -S reflector')
- sys.exit(1)
- print(f'{BRIGHT_BLUE}::{RESET} Updating pacman mirrors . . .')
- countries = generate_sorted_countries_list(USER_LAT, USER_LON, 8)
- print(f'Nearby Countries: {countries}')
- cmd = [
- 'sudo', 'reflector',
- '--verbose',
- '--country', countries,
- '--protocol', 'https',
- '--sort', 'rate',
- '--age', '1',
- '--save', MIRRORLIST_PATH,
- ]
- try:
- subprocess.run(cmd, check=True)
- print(f'{BRIGHT_GREEN}:: Mirrorlist {MIRRORLIST_PATH} updated successfully . . .{RESET}')
- except subprocess.CalledProcessError:
- print(f'{BRIGHT_RED}:: Failed to update mirrorlist {MIRRORLIST_PATH} . . .{RESET}')
- sys.exit(1)
- def generate_country_coords():
- # Ask reflector for supported countries.
- result = subprocess.run(
- ['reflector', '--list-countries'],
- capture_output=True, text=True, check=True
- )
- reflector_lines = result.stdout.splitlines()[3:] # Skip headers.
- valid_countries = {
- ' '.join(line.split()[:-2]) for line in reflector_lines if line.strip()
- }
- # Query RestCountries API for lat/lon.
- url = 'https://restcountries.com/v3.1/all?fields=name,latlng'
- resp = requests.get(url)
- resp.raise_for_status()
- data = resp.json()
- # Map country names to match reflector spelling.
- NAME_MAP = {
- 'Turkey': 'Türkiye',
- }
- # Print as Python dict.
- print('COUNTRY_COORDS = {')
- for country in sorted(data, key=lambda c: c.get('name', {}).get('common', '')):
- name = country.get('name', {}).get('common')
- latlng = country.get('latlng')
- if not (name and latlng):
- continue
- reflector_name = NAME_MAP.get(name, name)
- if reflector_name in valid_countries:
- lat, lon = latlng[0], latlng[1]
- print(f" '{reflector_name}': ({lat}, {lon}),")
- print('}')
- if __name__ == '__main__':
- # Uncomment to generate COUNTRY_COORDS dictionary.
- # generate_country_coords()
- # sys.exit(0)
- update_arch_mirrors()
- File: /usr/local/bin/yip
- USAGE: $ yip <-- Update installed packages, delete downloaded packages, purge the orphans, and check if the system can boot, like a boss.
- #!/bin/bash
- # Installation:
- # $ sudo cp ./yip /usr/local/bin/
- # $ sudo chmod +x /usr/local/bin/yip
- # ----- Colors -----
- BLACK="\033[30m"
- RED="\033[31m"
- GREEN="\033[32m"
- YELLOW="\033[33m"
- BLUE="\033[34m"
- MAGENTA="\033[35m"
- CYAN="\033[36m"
- WHITE="\033[37m"
- BRIGHT_BLACK="\033[90m"
- BRIGHT_RED="\033[91m"
- BRIGHT_GREEN="\033[92m"
- BRIGHT_YELLOW="\033[93m"
- BRIGHT_BLUE="\033[94m"
- BRIGHT_MAGENTA="\033[95m"
- BRIGHT_CYAN="\033[96m"
- BRIGHT_WHITE="\033[97m"
- # ----- Styles -----
- RESET="\033[0m" # Reset all styles.
- BOLD="\033[1m"
- HALF_BRIGHT="\033[2m"
- ITALIC="\033[3m"
- UNDERLINE="\033[4m"
- REVERSED="\033[7m" # Reverse foreground/background.
- # Prompt for sudo at the start.
- if ! sudo -v; then
- echo -e "${BRIGHT_BLUE}::${RESET} This script requires sudo privileges. Exiting."
- exit 1
- fi
- # Get the directory containing this script.
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- tty_check() {
- # If running in a graphical session.
- if [[ -n $DISPLAY ]]; then
- while true; do
- echo
- echo -e "${CYAN}:: WARNING: Close ALL programs before continuing. Ideally you should boot to a TTY session before running this script.${RESET}"
- echo -e "${CYAN} Having programs running during an update can cause errors due to old libraries still being in use.${RESET}"
- echo
- read -p "Do you want to continue? [Y/N]: " response
- # Set response to 'Y' if you didn't press Y|N so Enter allows you to continue like with Yay/Pacman.
- response=${response:-Y}
- case "$response" in
- y|Y) break ;;
- n|N) exit 1 ;; # Exit script with status 1 to indicate failure.
- *) echo; echo "Please answer Y or N." ;;
- esac
- done
- fi
- }
- tty_check
- check_for_update_news() {
- echo -e "${BRIGHT_BLUE}::${RESET} Checking Arch Linux RSS feed . . ."
- "$SCRIPT_DIR/yap" --current
- status=$?
- case $status in
- 2) # News detected.
- echo -e "${BRIGHT_CYAN}:: WARNING: News event detected.${RESET}"
- ;;
- 1) # Error detected.
- # No need for a error message because the called script will display its own.
- # echo -e "${BRIGHT_RED}:: ERROR: Could not fetch Arch Linux news!${RESET}
- ;;
- 0) # No news or error detected.
- return
- ;;
- *) # Unexpected exit code.
- echo -e "${BRIGHT_RED}:: Unknown error occurred (exit code $status)!${RESET}"
- ;;
- esac
- while true; do
- echo
- read -p "Do you want to continue? [Y/N]: " response
- response=${response:-Y}
- case "$response" in
- y|Y) break ;;
- n|N) exit 1 ;; # Exit script with status 1 to indicate failure.
- *) echo; echo "Please answer Y or N." ;;
- esac
- done
- }
- check_for_update_news
- free_space_check() {
- local directory=$1
- local low_space_mb=$2
- # If directory does not exist or is not a directory.
- if [[ ! -d "$directory" ]]; then
- echo
- echo -e "${RED}::${RESET} ERROR: Directory '$directory' does not exist."
- exit 1 # Exit script with status 1 to indicate failure.
- fi
- # Get all space values in bytes.
- local low_space_bytes=$(awk "BEGIN {print $low_space_mb * 1024 * 1024}") # Using awk because bash will overflow: $ echo $((9999999999999 * 1024 * 1024)) # will display -7960984073710600192.
- local free_space_bytes=$(df --block-size=1 --output=avail "$directory" 2>/dev/null | tail -n1)
- local total_space_bytes=$(df --block-size=1 --output=size "$directory" 2>/dev/null | tail -n1)
- # If free space is less than low space.
- if awk -v free="$free_space_bytes" -v low="$low_space_bytes" 'BEGIN { exit !(free < low) }'; then
- local si_scalars=(1208925819614629174706176 1180591620717411303424 1152921504606846976 1125899906842624 1099511627776 1073741824 1048576 1024)
- local si_prefixes=("YiB" "ZiB" "EiB" "PiB" "TiB" "GiB" "MiB" "KiB")
- # Find the appropriate SI prefix.
- local scalar=1
- local prefix="KiB"
- for i in ${!si_scalars[@]}; do
- if awk -v space="$total_space_bytes" -v scalar="${si_scalars[$i]}" 'BEGIN { exit !(space >= scalar) }'; then
- scalar="${si_scalars[$i]}"
- prefix="${si_prefixes[$i]}"
- break
- fi
- done
- # Calculate output space info.
- local free_space_scaled=$(awk -v bytes="$free_space_bytes" -v scalar="$scalar" 'BEGIN { printf "%.1f", bytes / scalar }')
- local total_space_scaled=$(awk -v bytes="$total_space_bytes" -v scalar="$scalar" 'BEGIN { printf "%.1f", bytes / scalar }')
- local used_percent=$(awk -v free="$free_space_bytes" -v total="$total_space_bytes" 'BEGIN { printf "%.f", (1 - free / total) * 100 }')
- while true; do
- # Displays disk space info like Dolphin properties window.
- echo
- echo -e "${RED}:: WARNING: Low disk space detected. ${free_space_scaled} ${prefix} free of ${total_space_scaled} ${prefix} (${used_percent}% used) on drive '${directory}'.${RESET}"
- echo -e "${RED} Running out of room during an update can cause incomplete installations, system crashes, or render the system unbootable.${RESET}"
- echo
- read -p "Do you want to continue? [Y/N]: " response
- # Set response to 'Y' if you didn't press Y|N so Enter allows you to continue like with Yay/Pacman.
- response=${response:-Y}
- case "$response" in
- y|Y) break ;;
- n|N) exit 1 ;; # Exit script with status 1 to indicate failure.
- *) echo; echo "Please answer Y or N." ;;
- esac
- done
- fi
- }
- # WIP low-space checks. Feel free to mod.
- free_space_check /efi 50 # If you use /boot/efi then change "/efi" to "/boot/efi".
- free_space_check /boot 500
- free_space_check / 10240
- free_space_check "$HOME" 5120 # Probably not needed for updates but important to use computer.
- is_grub_installed() {
- command -v grub-install &>/dev/null || find /boot /efi /EFI -type d -iname "grub" -quit &>/dev/null
- }
- is_refind_installed() {
- command -v refind-install &>/dev/null || find /boot /efi /EFI -type d -iname "refind" -quit &>/dev/null
- }
- is_systemd_boot_installed() {
- # If the bootctl command does not exist return false (1).
- # This command is very likely to exist since all systems that installed the "base" arch package have systemd which installs bootctl.
- command -v bootctl &>/dev/null || return 1
- # Ask bootctl if systemd-boot is installed, otherwise look for it.
- sudo bootctl is-installed &>/dev/null || find /boot /efi /EFI -type d -iname "loader" -quit &>/dev/null
- }
- is_pacman_hanging() {
- local wait_count=0
- local wait_time=7
- while pgrep -x "pacman" >/dev/null || [ -f /var/lib/pacman/db.lck ]; do
- case $wait_count in
- 0)
- # First time don't print any warning.
- ;;
- 1)
- echo
- echo -e "${RED}:: WARNING: Pacman is still running or its lock is still active. Waiting . . .${RESET}"
- wait_time=$((wait_time * 2))
- ;;
- 2)
- wait_time=1
- echo
- echo -e "${RED}Pacman seems to be misbehaving. Waiting . . .${RESET}"
- echo
- echo -e "${RED}If you restart now you might find an unbootable machine due to missing or outdated initramfs and kernel modules.${RESET}"
- echo
- echo -e "${RED}Here are some steps that might help:${RESET}"
- echo
- echo -e "${RED}1. Kill the running pacman process and remove the lock:${RESET}"
- echo -e "${RED} $ sudo killall pacman${RESET}"
- echo -e "${RED} $ sudo rm /var/lib/pacman/db.lck${RESET}"
- echo -e "${RED} This is necessary to run pacman again.${RESET}"
- echo
- echo -e "${RED}2. Reinstall critical packages:${RESET}"
- echo -e "${RED} $ sudo pacman -S udev mkinitcpio linux${RESET}"
- echo -e "${RED} $ sudo mkinitcpio -P${RESET}"
- echo -e "${RED} This will reinstall essential packages and ensure the kernel and initramfs are created.${RESET}"
- echo
- echo -e "${RED}3. Verify package integrity:${RESET}"
- echo -e "${RED} $ sudo pacman -Qkk${RESET}"
- echo -e "${RED} This will check for any missing files or integrity issues with installed packages.${RESET}"
- echo
- if is_grub_installed; then
- echo -e "${RED}4. Regenerate the GRUB configuration:${RESET}"
- echo -e "${RED} $ sudo grub-mkconfig -o /boot/grub/grub.cfg${RESET}"
- elif is_refind_installed; then
- echo -e "${RED}4. Regenerate the rEFInd configuration:${RESET}"
- echo -e "${RED} $ sudo refind-install${RESET}"
- elif is_systemd_boot_installed; then
- echo -e "${RED}4. Regenerate the systemd-boot configuration:${RESET}"
- echo -e "${RED} $ sudo bootctl update${RESET}"
- else
- echo -e "${RED}4. Reinstall your boot loader.${RESET}"
- fi
- echo
- echo -e "${RED}5. After performing these steps try running this script again.${RESET}"
- echo
- echo -e "${RED}Press Ctrl+C to stop waiting for pacman and exit this script.${RESET}"
- ;;
- esac
- sleep $wait_time
- ((wait_count++))
- done
- if ((wait_count > 1)); then
- echo -e "${GREEN}:: Pacman seems to have recovered. Continuing with the script . . .${RESET}"
- fi
- }
- update_arch() {
- # Refresh package databases and ensure keyring is up-to-date.
- # SOURCE: https://www.reddit.com/r/archlinux/comments/1leg9ds/why_doesnt_pacman_just_install_archlinuxkeyring/
- is_pacman_hanging
- sudo pacman -Sy --noconfirm --needed archlinux-keyring
- # sudo pacman -Syy --noconfirm --needed archlinux-keyring # Uses -Syy to force-refresh package databases.
- # Update packages including AUR.
- is_pacman_hanging
- yay -Syu
- # yay -Syu --noconfirm # Update everything without question.
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Searching for *.pacnew, *.pacorig, and *.pacsave files . . ."
- sudo find /etc /usr -type f \( -iname '*.pacnew' -o -iname '*.pacorig' -o -iname '*.pacsave' \) | grep --color=always -E '.*'
- }
- update_arch
- remove_orphaned_packages() {
- # Remove unneeded dependencies.
- # NOTE: Strangely both [$ yes | yay -Scc] and [$ yay -Scc --noconfirm] will prompt you to remove dependencies so [yay -Ycc --noconfirm] needs to be called first to prevent that prompt from displaying.
- is_pacman_hanging
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Remove unneeded dependencies . . ."
- yay -Ycc --noconfirm
- # Clean Pacman cache directory /var/cache/pacman/pkg/, Pacman database directory /var/lib/pacman/, and Yay AUR build directory $HOME/.cache/yay/.
- # If you need to downgrade or install an outdated package then you will need to download it again.
- # NOTE: To just clean the Pacman directories the command is [$ yes | sudo pacman -Scc].
- is_pacman_hanging
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Clean Pacman and Yay caches . . ."
- yes | yay -Scc
- # Purge the orphans.
- is_pacman_hanging
- mapfile -t orphans < <(pacman -Qdtq)
- if [[ ${#orphans[@]} -gt 0 ]]; then
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Removing orphaned packages . . ."
- # sudo pacman -Rns "${orphans[@]}"
- sudo pacman -Rns --noconfirm "${orphans[@]}"
- else
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} No orphaned packages found . . ."
- fi
- }
- remove_orphaned_packages
- remove_orphaned_kernel_modules() {
- # Build a map of installed kernel versions based on current /boot images.
- declare -A kernel_versions
- for img in /boot/vmlinuz-*; do
- [[ -f "$img" ]] || continue
- pkgname="${img#/boot/vmlinuz-}" # Strip '/boot/vmlinuz-' to get the kernel package name e.g. 'linux', 'linux-lts', 'linux-hardened', 'linux-zen' etc.
- # If a package named $pkgname is installed.
- if pacman -Q "$pkgname" &>/dev/null; then
- # Add an associative array entry to map a kernel package to its installed version.
- kernel_versions["$pkgname"]="$(pacman -Q "$pkgname" | awk '{print $2}')"
- fi
- done
- # Collect orphaned module directories.
- local orphans=()
- for dir in /usr/lib/modules/*/; do # The trailing '/' ensures only directories are matched.
- [[ -d "$dir" ]] || continue # Skip if this isn't a directory.
- module_dir=$(basename "$dir")
- skip=false
- for version in "${kernel_versions[@]}"; do
- [[ "$module_dir" == "$version" ]] && { skip=true; break; }
- done
- [[ $skip == true ]] && continue # Skip if this module's kernel is installed.
- # If no installed package owns this module directory.
- if ! pacman -Qo "/usr/lib/modules/${module_dir}" &>/dev/null; then
- orphans+=("${module_dir}") # Mark this module directory as orphaned.
- fi
- done
- # If no orphaned kernel modules found.
- if [[ ${#orphans[@]} -eq 0 ]]; then
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} No orphaned kernel modules found . . ."
- return
- fi
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Remove orphaned kernel modules . . ."
- # Print installed kernels.
- echo
- echo -e " ${BRIGHT_CYAN}Installed kernels:${RESET}"
- for pkgname in "${!kernel_versions[@]}"; do
- echo -e " ${BRIGHT_CYAN}${pkgname}: ${kernel_versions[$pkgname]}${RESET}"
- done
- # Print orphaned modules.
- echo
- echo -e " ${BRIGHT_RED}Orphaned kernel modules:${RESET}"
- for orphan in "${orphans[@]}"; do
- echo -e " ${BRIGHT_RED}\"/usr/lib/modules/${orphan}\"${RESET}"
- done
- # Read DKMS status once and collect a list of unique module names.
- local dkms_lines=()
- local modules=()
- if command -v dkms &>/dev/null; then
- mapfile -t dkms_lines < <(dkms status)
- mapfile -t modules < <(printf '%s\n' "${dkms_lines[@]}" | awk -F',' '{print $1}' | sort -u)
- fi
- # Remove orphaned kernel modules.
- local remove_all=false
- for orphan in "${orphans[@]}"; do
- echo
- echo -e " ${BRIGHT_YELLOW}Remove \"/usr/lib/modules/${orphan}\"${RESET}"
- # Show DKMS modules associated with this orphan.
- matched_modules=()
- if [[ ${#modules[@]} -gt 0 ]]; then
- # Loop through each unique module.
- for mod in "${modules[@]}"; do
- for line in "${dkms_lines[@]}"; do
- # If module is built for the orphaned kernel.
- if [[ "$line" == "${mod}, ${orphan},"* ]]; then
- matched_modules+=("$mod")
- break
- fi
- done
- done
- # If DKMS modules exist which are associated with the orphaned kernel.
- if [[ ${#matched_modules[@]} -gt 0 ]]; then
- echo
- echo -e " ${BRIGHT_YELLOW}Remove DKMS modules associated with '${orphan}':${RESET}"
- for mod in "${matched_modules[@]}"; do
- echo -e " ${BRIGHT_YELLOW}${mod}${RESET}"
- done
- fi
- fi
- # Ask the user if they want to delete or skip.
- if [[ $remove_all == false ]]; then
- while true; do
- echo
- read -p " Do you want to continue? [Y/N/A]: " response
- case "$response" in
- y|Y) break ;; # Remove this orphan.
- n|N) continue 2 ;; # Skip to next orphan.
- a|A) remove_all=true; break ;; # Remove all orphans.
- *) echo; echo " Please answer Y, N, or A." ;;
- esac
- done
- fi
- sudo rm -rf "/usr/lib/modules/${orphan}"
- # Remove DKMS records.
- if [[ ${#matched_modules[@]} -gt 0 ]]; then
- for mod in "${matched_modules[@]}"; do
- echo -e " ${BRIGHT_CYAN}Removing DKMS record for '${mod}' on '${orphan}'${RESET}"
- sudo dkms remove "${mod}" -k "${orphan}" --all
- done
- fi
- done
- echo
- echo -e " ${BRIGHT_GREEN}Orphaned kernel module cleanup complete . . .${RESET}"
- }
- remove_orphaned_kernel_modules
- is_system_bootable() {
- # Find kernel images.
- # local kernel_images=($(ls /boot/vmlinuz-* 2>/dev/null))
- local kernel_images=($(shopt -s nullglob; echo /boot/vmlinuz-*))
- if [[ ${#kernel_images[@]} -eq 0 ]]; then
- echo
- echo -e "${MAGENTA}:: WARNING: No kernel images found in /boot${RESET}"
- exit 1
- fi
- # Create a list of expected initramfs files based on the list of detected kernels.
- local initramfs_images=()
- for kernel in "${kernel_images[@]}"; do
- local base_name="${kernel#/boot/vmlinuz-}"
- initramfs_images+=("/boot/initramfs-${base_name}.img")
- initramfs_images+=("/boot/initramfs-${base_name}-fallback.img")
- done
- # Check for missing kernel and initramfs files.
- local missing_files=()
- for file in "${kernel_images[@]}" "${initramfs_images[@]}"; do
- [[ ! -f "$file" ]] && missing_files+=("$file")
- done
- # Check for missing bootloader files.
- if is_grub_installed; then
- local grub_cfg
- grub_cfg=$(find /boot /efi /EFI -type f -path "*/grub/grub.cfg" -print -quit 2>/dev/null)
- [[ -z "$grub_cfg" ]] && missing_files+=("esp/grub.cfg")
- elif is_refind_installed; then
- local refind_conf
- refind_conf=$(find /boot /efi /EFI -type f -path "*/refind/refind.conf" -print -quit 2>/dev/null)
- [[ -z "$refind_conf" ]] && missing_files+=("esp/refind/refind.conf")
- elif is_systemd_boot_installed; then
- local loader_conf
- loader_conf=$(find /boot /efi /EFI -type f -path "*/loader/loader.conf" -print -quit 2>/dev/null)
- [[ -z "$loader_conf" ]] && missing_files+=("esp/loader/loader.conf")
- if ! find /boot /efi /EFI -type f -path "*/loader/entries/*.conf" -quit &>/dev/null; then
- missing_files+=("esp/loader/entries/*.conf")
- fi
- fi
- if [[ ${#missing_files[@]} -ne 0 ]]; then
- echo
- echo -e "${MAGENTA}:: WARNING: System boot files are missing:${RESET}"
- for file in "${missing_files[@]}"; do
- echo -e "${MAGENTA} $file${RESET}"
- done
- echo
- echo -e "${BOLD}${GREEN} Suggested repairs:${RESET}"
- echo
- echo -e "${BOLD}${GREEN} $ sudo mkinitcpio -P${RESET}"
- echo
- if is_grub_installed; then
- [[ -z "$grub_cfg" ]] && echo -e "${BOLD}${GREEN} $ sudo grub-mkconfig -o /boot/grub/grub.cfg${RESET}" && echo
- elif is_refind_installed; then
- [[ -z "$refind_conf" ]] && echo -e "${BOLD}${GREEN} $ sudo refind-install${RESET}" && echo
- elif is_systemd_boot_installed; then
- [[ -z "$loader_conf" ]] && echo -e "${BOLD}${GREEN} $ sudo bootctl update${RESET}" && echo
- else
- echo -e "${BOLD}${GREEN} Reinstall your boot loader.${RESET}" && echo
- fi
- exit 1
- fi
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} No boot issues detected . . ."
- }
- is_system_bootable
- File: /usr/local/bin/yap
- USAGE: $ yap <-- Show recent Arch Linux news events, highlighting events since the last system upgrade.
- #!/usr/bin/env python3
- # Installation:
- # $ sudo cp ./yap /usr/local/bin/
- # $ sudo chmod +x /usr/local/bin/yap
- # ----- Colors -----
- BLACK = '\033[30m'
- RED = '\033[31m'
- GREEN = '\033[32m'
- YELLOW = '\033[33m'
- BLUE = '\033[34m'
- MAGENTA = '\033[35m'
- CYAN = '\033[36m'
- WHITE = '\033[37m'
- BRIGHT_BLACK = '\033[90m'
- BRIGHT_RED = '\033[91m'
- BRIGHT_GREEN = '\033[92m'
- BRIGHT_YELLOW = '\033[93m'
- BRIGHT_BLUE = '\033[94m'
- BRIGHT_MAGENTA = '\033[95m'
- BRIGHT_CYAN = '\033[96m'
- BRIGHT_WHITE = '\033[97m'
- # ----- Styles -----
- RESET = '\033[0m' # Reset all styles.
- BOLD = '\033[1m'
- HALF_BRIGHT = '\033[2m'
- ITALIC = '\033[3m'
- UNDERLINE = '\033[4m'
- REVERSED = '\033[7m' # Reverse foreground and background colors.
- import html
- import re
- import shutil
- import subprocess
- import sys
- import time
- import xml.etree.ElementTree as ET
- from datetime import datetime
- from pathlib import Path
- from urllib.error import URLError, HTTPError
- from urllib.request import urlopen, Request
- NEWS_FEED_URL = "https://archlinux.org/feeds/news/"
- PACMAN_LOG = "/var/log/pacman.log"
- import argparse
- def usage():
- parser = argparse.ArgumentParser(
- description="Show recent Arch Linux news events, highlighting ones since your last system upgrade.",
- add_help=False
- )
- parser.add_argument("--brief", action="store_true", help="Show titles and URLs only (omit descriptions).")
- parser.add_argument("--current", action="store_true", help="Only show news since your last full upgrade.")
- parser.add_argument("--max", type=int, default=0, help="Limit number of news items shown.")
- parser.add_argument("--debug", action="store_true", help="Force all news to appear new (for testing).")
- parser.add_argument("--help", "-h", action="help", help="Show this help message and exit.")
- try:
- global args
- args = parser.parse_args()
- except SystemExit:
- sys.exit(0) # No news.
- def get_last_upgrade_time(debug=False):
- if debug:
- return 0
- try:
- with open(PACMAN_LOG, "r") as f:
- for line in reversed(f.readlines()):
- if "starting full system upgrade" in line:
- ts_match = re.search(r"\[(.*?)\]", line)
- if ts_match:
- ts_str = ts_match.group(1)
- return int(datetime.strptime(ts_str, "%Y-%m-%dT%H:%M:%S%z").timestamp())
- except Exception:
- pass
- return 0 # Fallback if no match.
- def fetch_rss_feed(url):
- try:
- req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
- with urlopen(req, timeout=5) as response:
- return response.read().decode("utf-8")
- except HTTPError as e:
- print(f"{BRIGHT_RED}HTTP Error {e.code}: {e.reason}{RESET}", file=sys.stderr)
- except URLError as e:
- print(f"{BRIGHT_RED}URL Error: {e.reason}{RESET}", file=sys.stderr)
- return None
- def simplify_links(text):
- # # Convert <a href="url">text</a> to [text](url)
- # text = re.sub(r'<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)</a>', r'[\2](\1)', text)
- # # Simplify [text](url) to just text if text == url
- # text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', lambda m: m.group(1) if m.group(1) == m.group(2) else m.group(0), text)
- # Convert <a href="url">text</a> to [text](url), or just text if text == url.
- text = re.sub(
- r'<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)</a>',
- lambda m: m.group(1) if m.group(1) == m.group(2) else f'[{m.group(2)}]({m.group(1)})',
- text
- )
- # Unescape HTML entities recursively.
- prev = None
- while prev != text:
- prev = text
- text = html.unescape(text)
- # Strip remaining HTML tags.
- text = re.sub(r"<[^>]+>", "", text)
- return text.strip()
- def parse_feed(xml_data):
- root = ET.fromstring(xml_data)
- channel = root.find("channel")
- return channel.findall("item") if channel is not None else []
- def display_news(items, last_upgrade, brief=False, current_only=False, max_events=0):
- now = int(time.time())
- count = 0
- for item in items:
- title = simplify_links(item.findtext("title", default=""))
- link = simplify_links(item.findtext("link", default=""))
- desc = simplify_links(item.findtext("description", default=""))
- pub_date = item.findtext("pubDate", default="")
- try:
- pub_ts = int(datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S %z").timestamp())
- except Exception:
- continue
- if current_only and pub_ts <= last_upgrade:
- continue
- color = BRIGHT_RED if pub_ts > last_upgrade else BRIGHT_GREEN
- status = "[NEW] " if pub_ts > last_upgrade else ""
- days_ago = int((now - pub_ts) / (60 * 60 * 24) + 0.5)
- iso_date = datetime.fromtimestamp(pub_ts).strftime("%Y-%m-%d")
- print(f"\n{color}{status}D+{days_ago:03}/{iso_date} {title}{RESET}")
- print(f"{color}@ {RESET}{link}")
- if not brief:
- print(f"{color}>{RESET} {desc}")
- count += 1
- if max_events > 0 and count >= max_events:
- break
- return count > 0
- def wait_for_any_keypress():
- if sys.platform == 'win32':
- import os
- os.system('pause')
- elif sys.platform.startswith('linux') or sys.platform == 'darwin':
- print('Press any key to continue . . .')
- import termios
- import tty
- stdin_file_desc = sys.stdin.fileno()
- old_stdin_tty_attr = termios.tcgetattr(stdin_file_desc)
- try:
- tty.setraw(stdin_file_desc)
- sys.stdin.read(1)
- finally:
- termios.tcsetattr(stdin_file_desc, termios.TCSADRAIN, old_stdin_tty_attr)
- def main():
- usage()
- # Paging support.
- if sys.stdout.isatty() and "YAP_IN_LESS" not in os.environ and len(sys.argv) == 1:
- os.environ["YAP_IN_LESS"] = "1"
- if shutil.which("less"):
- pager = ["less", "-R"]
- shell = False
- else:
- pager = None
- shell = False
- if pager:
- proc = subprocess.Popen([sys.executable] + sys.argv, stdout=subprocess.PIPE)
- try:
- subprocess.run(pager, stdin=proc.stdout, shell=shell)
- except KeyboardInterrupt:
- pass
- if sys.platform != 'win32':
- proc.wait()
- code = proc.returncode
- if code in (0, 2): # Preserve 0 (no news) / 1 (error) / 2 (news) codes.
- sys.exit(code)
- else: # Convert all other return codes e.g. if the child process was killed by a signal, proc.returncode will be negative, to 1 (error).
- sys.exit(1) # Error.
- else:
- sys.exit(0) # No news.
- # Fetch feed and display.
- feed = fetch_rss_feed(NEWS_FEED_URL)
- if feed is None:
- sys.exit(1) # Error.
- last_upgrade = 0 if args.debug else get_last_upgrade_time()
- from datetime import datetime
- from time import time
- upgrade_iso = datetime.fromtimestamp(last_upgrade).isoformat()
- days_ago = int((time() - last_upgrade) / (60 * 60 * 24) + 0.5)
- print(f"{BRIGHT_GREEN}Last full system upgrade:{RESET} D+{days_ago:03}/{upgrade_iso}")
- items = parse_feed(feed)
- try:
- success = display_news(items, last_upgrade, brief=args.brief, current_only=args.current, max_events=args.max)
- except BrokenPipeError:
- try:
- sys.stdout.close()
- except Exception:
- pass
- finally:
- sys.exit(0) # No news. The user quit by pressing Q.
- if success:
- sys.exit(2) # News.
- else:
- if args.current:
- print("No news detected since last upgrade")
- else:
- print("No news items found in the feed")
- sys.exit(0) # No news.
- if __name__ == "__main__":
- import os
- main()
- File: /usr/local/bin/yow
- USAGE: $ yow <-- Show a colorized version of /var/log/pacman.log.
- #!/bin/bash
- # Installation:
- # $ sudo cp ./yow /usr/local/bin/
- # $ sudo chmod +x /usr/local/bin/yow
- # ----- Colors -----
- BLACK="\033[30m"
- RED="\033[31m"
- GREEN="\033[32m"
- YELLOW="\033[33m"
- BLUE="\033[34m"
- MAGENTA="\033[35m"
- CYAN="\033[36m"
- WHITE="\033[37m"
- BRIGHT_BLACK="\033[90m"
- BRIGHT_RED="\033[91m"
- BRIGHT_GREEN="\033[92m"
- BRIGHT_YELLOW="\033[93m"
- BRIGHT_BLUE="\033[94m"
- BRIGHT_MAGENTA="\033[95m"
- BRIGHT_CYAN="\033[96m"
- BRIGHT_WHITE="\033[97m"
- # ----- Styles -----
- RESET="\033[0m" # Reset all styles.
- BOLD="\033[1m"
- HALF_BRIGHT="\033[2m"
- ITALIC="\033[3m"
- UNDERLINE="\033[4m"
- REVERSED="\033[7m" # Reverse foreground/background.
- PACMAN_LOG='/var/log/pacman.log'
- # ----- Members -----
- display_help() {
- cat <<EOF
- Display and colorize $PACMAN_LOG entries.
- Usage: $ yow [OPTION]
- NOTE: If no option is provided then today's $(date +%Y-%m-%d) pacman.log entries will be displayed.
- Options:
- --all Show the entire pacman log colorized.
- --date=YYYY-MM-DD Show entries for the given date.
- --day=N Show entries for N days ago.
- --day=today|yesterday Show entries for the named day.
- --since=YYYY-MM-DD Show entries starting from the given date.
- --since=N Show entries starting from N days ago.
- --since=today|yesterday Show entries starting from the named day.
- --today Alias for --day=today.
- --yesterday Alias for --day=yesterday.
- -h, --help Show this help message and exit.
- EOF
- }
- colorize_line() {
- local line="$1"
- case "$line" in
- *"error:"*|*"warning:"*)
- echo -e "${BRIGHT_YELLOW}${line}${RESET}" ;;
- *"[ALPM-SCRIPTLET]"*|*".pacnew"*|*".pacsave"*|*".pacorig"*)
- echo -e "${BRIGHT_BLACK}${line}${RESET}" ;;
- *"[ALPM] installed "*|*"[ALPM] upgraded "*)
- echo -e "${BRIGHT_CYAN}${line}${RESET}" ;;
- *"[ALPM] removed "*)
- echo -e "${BRIGHT_RED}${line}${RESET}" ;;
- *"[ALPM] downgraded "*)
- echo -e "${BRIGHT_MAGENTA}${line}${RESET}" ;;
- *)
- echo -e "$line" ;;
- esac
- }
- normalize_date() {
- local input="$1"
- date -d "$input" +%Y-%m-%d 2>/dev/null || return 1
- }
- resolve_relative_date() {
- local value="$1"
- case "$value" in
- today) date +%Y-%m-%d ;;
- yesterday) date -d "yesterday" +%Y-%m-%d ;;
- ''|*[!0-9]*) echo "$value" ;; # Already a date string like "2025-08-01".
- *) date -d "$value days ago" +%Y-%m-%d ;;
- esac
- }
- create_date_label() {
- local log_date="$1"
- local now_ts log_ts days_diff
- now_ts=$(date +%s)
- log_ts=$(date -d "$log_date" +%s 2>/dev/null) || return
- days_diff=$(( (log_ts - now_ts) / 86400 ))
- case $days_diff in
- -1) echo "YESTERDAY/" ;;
- 0) echo "TODAY/" ;;
- 1) echo "TOMORROW/" ;;
- *)
- if (( days_diff < 0 )); then
- echo "D-${days_diff#-}/"
- else
- echo "D+${days_diff}/"
- fi ;;
- esac
- }
- display_pacman_log() {
- local log_date="$1"
- local mode="${2:-exact}"
- if [[ ! -s "$PACMAN_LOG" ]]; then
- echo "ERROR: Cannot read or empty log: $PACMAN_LOG" >&2
- exit 1
- fi
- if [[ "$mode" == "since" ]]; then
- local current_date="$log_date"
- local today=$(date +%Y-%m-%d)
- local end_date=$(date -d "$today +1 day" +%Y-%m-%d)
- {
- while [[ "$current_date" < "$end_date" ]]; do
- local label=$(create_date_label "$current_date")
- local header="${BRIGHT_BLUE}::${RESET} Viewing $PACMAN_LOG entries for ${label}${current_date}"
- local matches
- matches=$(grep -F -- "$current_date" "$PACMAN_LOG" 2>/dev/null || true)
- if [[ -n "$matches" ]]; then
- echo -e "${header}\n"
- echo "$matches"
- echo
- fi
- current_date=$(date -d "$current_date +1 day" +%Y-%m-%d)
- done
- } | while IFS= read -r line; do
- colorize_line "$line"
- done | less -R
- else
- local label=$(create_date_label "$log_date")
- local header="${BRIGHT_BLUE}::${RESET} Viewing $PACMAN_LOG entries for ${label}${log_date}"
- local matches=$(grep -F -- "$log_date" "$PACMAN_LOG")
- if [[ -z "$matches" ]]; then
- echo -e "${BRIGHT_BLUE}::${RESET} No entries found for ${label}${log_date} in $PACMAN_LOG"
- exit 0
- fi
- {
- echo -e "${header}\n"
- echo "$matches"
- } | while IFS= read -r line; do
- colorize_line "$line"
- done | less -R
- fi
- }
- # ----- Parse arguments -----
- parse_date_arg() {
- local key="$1"
- local value="$2"
- if [[ -z "$value" ]]; then
- echo "ERROR: Missing value for --$key" >&2
- exit 1
- fi
- value=$(resolve_relative_date "$value")
- if ! norm=$(normalize_date "$value"); then
- echo "ERROR: Invalid date format: '$value'" >&2
- exit 1
- fi
- [[ "$key" == "since" ]] && mode="since" || mode="exact"
- display_pacman_log "$norm" "$mode"
- exit 0
- }
- # --key=value handling.
- if [[ "${1:-}" =~ ^--(date|day|since)=(.*)$ ]]; then
- parse_date_arg "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
- fi
- # --key value handling.
- case "${1:-}" in
- -h|--help)
- display_help ;;
- --all)
- first_date=$(grep -o '^\[[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}' "$PACMAN_LOG" | head -n1 | tr -d '[')
- if ! norm=$(normalize_date "$first_date"); then
- echo "ERROR: Could not determine start date of $PACMAN_LOG" >&2
- exit 1
- fi
- display_pacman_log "$norm" since ;;
- --today|"")
- display_pacman_log "$(date +%Y-%m-%d)" exact ;;
- --yesterday)
- display_pacman_log "$(date -d yesterday +%Y-%m-%d)" exact ;;
- --date|--since|--day)
- key="${1#--}"
- shift
- if [[ -z "${1:-}" ]]; then
- echo "ERROR: Missing value for --$key" >&2
- exit 1
- fi
- value="$1"
- parse_date_arg "$key" "$value" ;;
- *)
- echo "ERROR: Unknown option '${1:-}'" >&2
- echo "Try '$ yow --help' for usage." >&2
- exit 1 ;;
- esac
- File: /usr/local/bin/tug
- USAGE: $ tug <-- Updates my vim/nvim plugins.
- #!/bin/bash
- # Installation:
- # $ sudo cp ./tug /usr/local/bin/
- # $ sudo chmod +x /usr/local/bin/tug
- # ----- Colors -----
- BLACK="\033[30m"
- RED="\033[31m"
- GREEN="\033[32m"
- YELLOW="\033[33m"
- BLUE="\033[34m"
- MAGENTA="\033[35m"
- CYAN="\033[36m"
- WHITE="\033[37m"
- BRIGHT_BLACK="\033[90m"
- BRIGHT_RED="\033[91m"
- BRIGHT_GREEN="\033[92m"
- BRIGHT_YELLOW="\033[93m"
- BRIGHT_BLUE="\033[94m"
- BRIGHT_MAGENTA="\033[95m"
- BRIGHT_CYAN="\033[96m"
- BRIGHT_WHITE="\033[97m"
- # ----- Styles -----
- RESET="\033[0m" # Reset all styles.
- BOLD="\033[1m"
- HALF_BRIGHT="\033[2m"
- ITALIC="\033[3m"
- UNDERLINE="\033[4m"
- REVERSED="\033[7m" # Reverse foreground/background.
- # Prompt for sudo at the start.
- if ! sudo -v; then
- echo -e "${BRIGHT_BLUE}::${RESET} This script requires sudo privileges. Exiting."
- exit 1
- fi
- update_vim_plugins() {
- # ----- Vim -----
- if command -v vim &>/dev/null && [ -f ~/.vim/autoload/plug.vim ]; then
- echo -e "${BRIGHT_BLUE}::${RESET} Updating user vim plugins . . ."
- # Update ~/.vim/ plugins.
- vim -es +'silent! PlugUpgrade' +'silent! PlugUpdate' +qall
- echo
- if sudo -n true 2>/dev/null; then
- echo -e "${BRIGHT_BLUE}::${RESET} Updating root vim plugins . . ."
- # Update /root/.vim/ plugins.
- # -E is needed so the environment variables are available to the sudo instance of vim.
- # -H is needed so $HOME=/root instead of ~/home/wolf (a side effect of using the -E option).
- sudo -EH vim -es +'silent! PlugUpgrade' +'silent! PlugUpdate' +qall
- else
- echo -e "${BRIGHT_BLUE}::${RESET} Skipping root vim plugins (no sudo)."
- fi
- fi
- # ----- Neovim -----
- if command -v nvim &>/dev/null && [ -f ~/.local/share/nvim/site/autoload/plug.vim ]; then
- echo
- echo -e "${BRIGHT_BLUE}::${RESET} Updating user nvim plugins . . ."
- nvim --headless +'silent! PlugUpgrade' +'silent! PlugUpdate' +qall
- echo
- if sudo -n true 2>/dev/null; then
- echo -e "${BRIGHT_BLUE}::${RESET} Updating root nvim plugins . . ."
- sudo -EH nvim --headless +'silent! PlugUpgrade' +'silent! PlugUpdate' +qall
- else
- echo -e "${BRIGHT_BLUE}::${RESET} Skipping root nvim plugins (no sudo)."
- fi
- fi
- }
- update_vim_plugins
Advertisement
Add Comment
Please, Sign In to add comment