diff --git a/fresh-install/detect_gpu.sh b/fresh-install/detect_gpu.sh index e23c4a4..cc2eb96 100755 --- a/fresh-install/detect_gpu.sh +++ b/fresh-install/detect_gpu.sh @@ -2,17 +2,20 @@ # Lightweight GPU detection script. # Detects GPU vendor and invokes the corresponding vendor install/management script. # Exports: GPU_VENDOR +# shellcheck source=./install_nvidia_driver.sh +# shellcheck source=./install_amd_driver.sh +# shellcheck source=./install_intel_driver.sh set -e GPU_VENDOR="unknown" PCI_GPU_INFO=$(lspci -nn | grep -Ei 'vga|3d|display' || true) if echo "$PCI_GPU_INFO" | grep -qi nvidia; then - GPU_VENDOR="nvidia" + GPU_VENDOR="nvidia" elif echo "$PCI_GPU_INFO" | grep -Eqi '\b(amd|advanced micro devices|ati)\b'; then - GPU_VENDOR="amd" + GPU_VENDOR="amd" elif echo "$PCI_GPU_INFO" | grep -qi intel; then - GPU_VENDOR="intel" + GPU_VENDOR="intel" fi export GPU_VENDOR @@ -21,6 +24,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" case "$GPU_VENDOR" in nvidia) if [ -x "$SCRIPT_DIR/install_nvidia_driver.sh" ]; then + # shellcheck source=./install_nvidia_driver.sh disable=SC1091 . "$SCRIPT_DIR/install_nvidia_driver.sh" else echo "NVIDIA installer script missing: $SCRIPT_DIR/install_nvidia_driver.sh" @@ -28,6 +32,7 @@ case "$GPU_VENDOR" in ;; amd) if [ -x "$SCRIPT_DIR/install_amd_driver.sh" ]; then + # shellcheck source=./install_amd_driver.sh disable=SC1091 . "$SCRIPT_DIR/install_amd_driver.sh" else echo "AMD installer script missing: $SCRIPT_DIR/install_amd_driver.sh (placeholder)" @@ -35,6 +40,7 @@ case "$GPU_VENDOR" in ;; intel) if [ -x "$SCRIPT_DIR/install_intel_driver.sh" ]; then + # shellcheck source=./install_intel_driver.sh disable=SC1091 . "$SCRIPT_DIR/install_intel_driver.sh" else echo "Intel installer script missing: $SCRIPT_DIR/install_intel_driver.sh" diff --git a/fresh-install/detect_gpu_and_install.sh b/fresh-install/detect_gpu_and_install.sh index 03fab11..767dec9 100755 --- a/fresh-install/detect_gpu_and_install.sh +++ b/fresh-install/detect_gpu_and_install.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash # Backwards compatibility wrapper; prefer using detect_gpu.sh directly. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./detect_gpu.sh disable=SC1091 . "$SCRIPT_DIR/detect_gpu.sh" diff --git a/fresh-install/install_amd_driver.sh b/fresh-install/install_amd_driver.sh index 77c9bdc..6cd3711 100755 --- a/fresh-install/install_amd_driver.sh +++ b/fresh-install/install_amd_driver.sh @@ -12,7 +12,10 @@ # AMD_VERBOSE=1 # verbose output set -e -[ "${GPU_VENDOR}" = "amd" ] || { echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}"; exit 0; } +[ "${GPU_VENDOR}" = "amd" ] || { + echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}" + exit 0 +} AMD_INSTALL_XF86=${AMD_INSTALL_XF86:-0} AMD_INSTALL_AMDVLK=${AMD_INSTALL_AMDVLK:-0} @@ -55,19 +58,22 @@ LIB32_AMDVLK_PKG="lib32-amdvlk" # Simple AUR builder (reused from NVIDIA script style) _build_aur_pkg() { - local pkg="$1" url="https://aur.archlinux.org/${pkg}.git" - mkdir -p "$HOME/aur"; cd "$HOME/aur" + local pkg="$1" + local url="https://aur.archlinux.org/${pkg}.git" + mkdir -p "$HOME/aur" + cd "$HOME/aur" if [ ! -d "$pkg" ]; then git clone "$url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi - cd "$pkg"; rm -f -- *.pkg.tar.* 2>/dev/null || true + cd "$pkg" + rm -f -- *.pkg.tar.* 2> /dev/null || true yes | makepkg -s -c -C --noconfirm --needed - local built=( *.pkg.tar.zst ) + local built=(*.pkg.tar.zst) yes | sudo pacman -U --noconfirm "${built[@]}" } _install_repo_or_aur() { local pkg="$1" - if pacman -Si "$pkg" >/dev/null 2>&1; then - if pacman -Qi "$pkg" >/dev/null 2>&1; then + if pacman -Si "$pkg" > /dev/null 2>&1; then + if pacman -Qi "$pkg" > /dev/null 2>&1; then vlog "$pkg already installed" else yes | sudo pacman -Sy --noconfirm "$pkg" @@ -101,7 +107,8 @@ fi GPU_LINES=$(lspci -nn | grep -Ei 'vga|3d|display' | grep -iE 'amd|ati' || true) SI_NAMES=(Tahiti Pitcairn Cape Verde Oland Hainan Curacao) CIK_NAMES=(Bonaire Hawaii Kabini Kaveri Mullins Temash Spectre Spooky) -IS_SI=0; IS_CIK=0 +IS_SI=0 +IS_CIK=0 for n in "${SI_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_SI=1 && break; done for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done @@ -135,7 +142,7 @@ else fi # Check active kernel driver -KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}') +KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}') [ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}') info "Kernel driver in use: ${KDRV:-unknown}" diff --git a/fresh-install/install_intel_driver.sh b/fresh-install/install_intel_driver.sh index 096a548..f37c0e8 100755 --- a/fresh-install/install_intel_driver.sh +++ b/fresh-install/install_intel_driver.sh @@ -12,7 +12,10 @@ # INTEL_VERBOSE=0/1 # verbose logging set -e -[ "$GPU_VENDOR" = "intel" ] || { echo "Intel installer invoked but GPU_VENDOR=$GPU_VENDOR"; exit 0; } +[ "$GPU_VENDOR" = "intel" ] || { + echo "Intel installer invoked but GPU_VENDOR=$GPU_VENDOR" + exit 0 +} INTEL_USE_AMBER=${INTEL_USE_AMBER:-0} INTEL_INSTALL_LIB32=${INTEL_INSTALL_LIB32:-auto} @@ -41,10 +44,10 @@ fi install_pkg() { local pkg="$1" - if pacman -Qi "$pkg" >/dev/null 2>&1; then + if pacman -Qi "$pkg" > /dev/null 2>&1; then vlog "$pkg already installed" else - if pacman -Si "$pkg" >/dev/null 2>&1; then + if pacman -Si "$pkg" > /dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$pkg" else warn "Package $pkg not found in repos (not handling AUR here)" @@ -86,7 +89,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then else info "Configuring enable_guc=$INTEL_ENABLE_GUC" sudo mkdir -p /etc/modprobe.d - echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf >/dev/null + echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf > /dev/null if [ "$INTEL_SKIP_INITRAMFS" != 1 ] && [ -f /etc/mkinitcpio.conf ]; then info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change" sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually" @@ -97,7 +100,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then fi # Report kernel driver -KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}') +KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}') [ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'i915|xe' | head -n1 | awk '{print $1}') info "Kernel driver in use: ${KDRV:-unknown}" diff --git a/fresh-install/install_nvidia_driver.sh b/fresh-install/install_nvidia_driver.sh index ff174b7..ee126dc 100755 --- a/fresh-install/install_nvidia_driver.sh +++ b/fresh-install/install_nvidia_driver.sh @@ -4,15 +4,22 @@ # Outputs: NVIDIA_DRIVER_PACKAGE set -e -[ "$GPU_VENDOR" = "nvidia" ] || { echo "NVIDIA installer invoked but GPU_VENDOR=$GPU_VENDOR"; exit 0; } +[ "$GPU_VENDOR" = "nvidia" ] || { + echo "NVIDIA installer invoked but GPU_VENDOR=$GPU_VENDOR" + exit 0 +} _build_aur_pkg() { - local pkg="$1"; local repo_url="https://aur.archlinux.org/${pkg}.git"; - mkdir -p "$HOME/aur"; cd "$HOME/aur"; + local pkg="$1" + local repo_url="https://aur.archlinux.org/${pkg}.git" + mkdir -p "$HOME/aur" + cd "$HOME/aur" if [ ! -d "$pkg" ]; then git clone "$repo_url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi - cd "$pkg"; rm -f -- *.pkg.tar.* 2>/dev/null || true + cd "$pkg" + rm -f -- *.pkg.tar.* 2> /dev/null || true yes | makepkg -s -c -C --noconfirm --needed || return 1 - local built=( *.pkg.tar.zst ); yes | sudo pacman -U --noconfirm "${built[@]}" + local built=(*.pkg.tar.zst) + yes | sudo pacman -U --noconfirm "${built[@]}" } _choose_nvidia_pkg() { @@ -23,8 +30,8 @@ _choose_nvidia_pkg() { if [ $((have_linux + have_linux_lts)) -gt 1 ]; then multiple_kernels=1; else multiple_kernels=0; fi # Optionally skip attempting to install nvidia-detect (some minimal repo setups don't have it yet) - if [ -z "${NVIDIA_SKIP_DETECT:-}" ] && ! command -v nvidia-detect >/dev/null 2>&1; then - if pacman -Si nvidia-detect >/dev/null 2>&1; then + if [ -z "${NVIDIA_SKIP_DETECT:-}" ] && ! command -v nvidia-detect > /dev/null 2>&1; then + if pacman -Si nvidia-detect > /dev/null 2>&1; then echo "Attempting to install helper utility: nvidia-detect" >&2 # Use --needed to avoid forcing refresh (& avoid partial upgrade semantics with -Sy) yes | sudo pacman -S --needed --noconfirm nvidia-detect || echo "nvidia-detect install failed (continuing with heuristic)" >&2 @@ -33,25 +40,34 @@ _choose_nvidia_pkg() { fi fi - if command -v nvidia-detect >/dev/null 2>&1; then - detect_out="$(nvidia-detect 2>/dev/null || true)" + if command -v nvidia-detect > /dev/null 2>&1; then + detect_out="$(nvidia-detect 2> /dev/null || true)" fi if [ -n "$detect_out" ]; then - if echo "$detect_out" | grep -q '470'; then driver_pkg='nvidia-470xx-dkms'; legacy_detected=1; fi - if echo "$detect_out" | grep -q '390'; then driver_pkg='nvidia-390xx-dkms'; legacy_detected=1; fi - if echo "$detect_out" | grep -q '340'; then driver_pkg='nvidia-340xx-dkms'; legacy_detected=1; fi + if echo "$detect_out" | grep -q '470'; then + driver_pkg='nvidia-470xx-dkms' + legacy_detected=1 + fi + if echo "$detect_out" | grep -q '390'; then + driver_pkg='nvidia-390xx-dkms' + legacy_detected=1 + fi + if echo "$detect_out" | grep -q '340'; then + driver_pkg='nvidia-340xx-dkms' + legacy_detected=1 + fi fi if [ "$legacy_detected" = 0 ]; then # Heuristic modern driver selection if [ "$multiple_kernels" = 1 ]; then - if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-dkms >/dev/null 2>&1; then driver_pkg='nvidia-open-dkms'; else driver_pkg='nvidia-dkms'; fi + if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-dkms > /dev/null 2>&1; then driver_pkg='nvidia-open-dkms'; else driver_pkg='nvidia-dkms'; fi else if [ "$have_linux_lts" = 1 ] && [ "$have_linux" = 0 ]; then - if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-lts >/dev/null 2>&1; then driver_pkg='nvidia-open-lts'; else driver_pkg='nvidia-lts'; fi + if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-lts > /dev/null 2>&1; then driver_pkg='nvidia-open-lts'; else driver_pkg='nvidia-lts'; fi else - if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open >/dev/null 2>&1; then driver_pkg='nvidia-open'; else driver_pkg='nvidia'; fi + if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open > /dev/null 2>&1; then driver_pkg='nvidia-open'; else driver_pkg='nvidia'; fi fi fi else @@ -62,21 +78,22 @@ _choose_nvidia_pkg() { } _remove_conflicting_nvidia_pkgs() { - local keep="$1"; local candidates=(nvidia nvidia-lts nvidia-dkms nvidia-open nvidia-open-lts nvidia-open-dkms nvidia-470xx-dkms nvidia-390xx-dkms nvidia-340xx-dkms) + local keep="$1" + local candidates=(nvidia nvidia-lts nvidia-dkms nvidia-open nvidia-open-lts nvidia-open-dkms nvidia-470xx-dkms nvidia-390xx-dkms nvidia-340xx-dkms) local to_remove=() for p in "${candidates[@]}"; do - if pacman -Qi "$p" >/dev/null 2>&1 && [ "$p" != "$keep" ]; then to_remove+=("$p"); fi + if pacman -Qi "$p" > /dev/null 2>&1 && [ "$p" != "$keep" ]; then to_remove+=("$p"); fi done if [ ${#to_remove[@]} -gt 0 ]; then yes | sudo pacman -Rns --noconfirm "${to_remove[@]}" || true; fi } _install_nvidia_stack() { local driver_pkg="$1" - if [[ "$driver_pkg" == nvidia-*xx-dkms ]]; then _build_aur_pkg "$driver_pkg"; else yes | sudo pacman -Sy --noconfirm "$driver_pkg"; fi + if [[ $driver_pkg == nvidia-*xx-dkms ]]; then _build_aur_pkg "$driver_pkg"; else yes | sudo pacman -Sy --noconfirm "$driver_pkg"; fi local utils_pkg="nvidia-utils" utils32_pkg="lib32-nvidia-utils" - if ! pacman -Qi "$utils_pkg" >/dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils_pkg"; fi + if ! pacman -Qi "$utils_pkg" > /dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils_pkg"; fi if grep -q '^\[multilib\]' /etc/pacman.conf; then - if ! pacman -Qi "$utils32_pkg" >/dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils32_pkg" || true; fi + if ! pacman -Qi "$utils32_pkg" > /dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils32_pkg" || true; fi fi } diff --git a/fresh-install/main.sh b/fresh-install/main.sh index 9b0d6b7..9dec655 100755 --- a/fresh-install/main.sh +++ b/fresh-install/main.sh @@ -1,119 +1,124 @@ -#!/bin/sh +#!/usr/bin/env bash +# shellcheck source=./detect_gpu.sh +# shellcheck source=./detect_gpu_and_install.sh set -e # Function to play a sound on error play_error_sound() { - #pactl set-sink-volume @DEFAULT_SINK@ +50% - for i in 1 2 3; do - paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga - done - #pactl set-sink-volume @DEFAULT_SINK@ -50% + #pactl set-sink-volume @DEFAULT_SINK@ +50% + for _ in 1 2 3; do + paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga + done + #pactl set-sink-volume @DEFAULT_SINK@ -50% } # Trap errors and call the play_error_sound function trap 'play_error_sound' ERR - sudo -v git config --global init.defaultBranch main # GPU detection (now split vendor-specific logic) if [ -f "./detect_gpu.sh" ]; then - . ./detect_gpu.sh + # shellcheck source=./detect_gpu.sh disable=SC1091 + . ./detect_gpu.sh elif [ -f "./detect_gpu_and_install.sh" ]; then - . ./detect_gpu_and_install.sh + # shellcheck source=./detect_gpu_and_install.sh disable=SC1091 + . ./detect_gpu_and_install.sh else - echo "GPU detection scripts not found; continuing without GPU specific installation." + echo "GPU detection scripts not found; continuing without GPU specific installation." fi install_from_aur() { - if [ ! -d "$HOME/aur" ]; then - mkdir -p "$HOME/aur" - fi - cd "$HOME/aur" - local repo_url=$1 - local pkg_name=$2 - local repo_dir="$(basename "$repo_url" .git)" + local repo_url pkg_name repo_dir + repo_url="$1" + pkg_name="$2" - if [ ! -d "$repo_dir" ]; then - git clone "$repo_url" - else - echo "Repository $repo_dir already cloned; updating" - (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) - fi - cd "$repo_dir" + mkdir -p "$HOME/aur" + cd "$HOME/aur" || return 1 + repo_dir="$(basename "$repo_url" .git)" - if pacman -Qi "$pkg_name" >/dev/null 2>&1; then - echo "$pkg_name is already installed" - return 0 - fi + if [ ! -d "$repo_dir" ]; then + git clone "$repo_url" + else + echo "Repository $repo_dir already cloned; updating" + (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) + fi + cd "$repo_dir" || return 1 - echo "Cleaning old package artifacts to avoid duplicate -U targets" - find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true + if pacman -Qi "$pkg_name" > /dev/null 2>&1; then + echo "$pkg_name is already installed" + return 0 + fi - echo "Building $pkg_name (clean build)" - # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) - if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then - echo "Build failed for $pkg_name" >&2 - return 1 - fi + echo "Cleaning old package artifacts to avoid duplicate -U targets" + find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true - # Collect only the freshly built packages (should now be only current version) - mapfile -t built_pkgs < <(ls -1 *.pkg.tar.zst 2>/dev/null || true) - if [ ${#built_pkgs[@]} -eq 0 ]; then - echo "No package files produced for $pkg_name" >&2 - return 1 - fi + echo "Building $pkg_name (clean build)" + # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) + if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then + echo "Build failed for $pkg_name" >&2 + return 1 + fi - echo "Installing built package(s): ${built_pkgs[*]}" - if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then - echo "Installation failed for $pkg_name" >&2 - return 1 - fi + # Collect only the freshly built packages (should now be only current version) + mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') + if [ ${#built_pkgs[@]} -eq 0 ]; then + echo "No package files produced for $pkg_name" >&2 + return 1 + fi + + echo "Installing built package(s): ${built_pkgs[*]}" + if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then + echo "Installation failed for $pkg_name" >&2 + return 1 + fi } process_packages() { - local file_path=$1 - > failed.txt - > done.txt + local file_path + file_path="$1" + : > failed.txt + : > done.txt - while IFS= read -r pkg_name; do - if [ -z "$pkg_name" ]; then - continue - fi + while IFS= read -r pkg_name; do + if [ -z "$pkg_name" ]; then + continue + fi - local repo_url="https://aur.archlinux.org/${pkg_name}-git.git" - local repo_dir="${pkg_name}-git" + local repo_url repo_dir + repo_url="https://aur.archlinux.org/${pkg_name}-git.git" + repo_dir="${pkg_name}-git" - git clone $repo_url - if [ -d "$repo_dir" ] && [ -z "$(ls -A $repo_dir)" ]; then - echo "Repository $repo_dir is empty, trying without -git suffix" - repo_url="https://aur.archlinux.org/${pkg_name}.git" - repo_dir="${pkg_name}" + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying without -git suffix" + repo_url="https://aur.archlinux.org/${pkg_name}.git" + repo_dir="${pkg_name}" - git clone $repo_url - if [ -d "$repo_dir" ] && [ -z "$(ls -A $repo_dir)" ]; then - echo "Repository $repo_dir is empty, trying to install with pacman" - if sudo pacman -Sy --noconfirm $pkg_name; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi - else - if install_from_aur $repo_url $pkg_name; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi - fi + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying to install with pacman" + if sudo pacman -Sy --noconfirm "$pkg_name"; then + echo "$pkg_name" >> done.txt else - if install_from_aur $repo_url $pkg_name; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi + echo "$pkg_name" >> failed.txt fi - done < "$file_path" + else + if install_from_aur "$repo_url" "$pkg_name"; then + echo "$pkg_name" >> done.txt + else + echo "$pkg_name" >> failed.txt + fi + fi + else + if install_from_aur "$repo_url" "$pkg_name"; then + echo "$pkg_name" >> done.txt + else + echo "$pkg_name" >> failed.txt + fi + fi + done < "$file_path" } sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak @@ -124,161 +129,175 @@ sudo cp ./pacman.conf /etc/pacman.conf # sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf # mkinitcpio -P # Reflector install / service management (idempotent & resilient) -if pacman -Qi reflector >/dev/null 2>&1; then - echo "reflector already installed" +if pacman -Qi reflector > /dev/null 2>&1; then + echo "reflector already installed" else - yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)" + yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)" fi # Prefer timer over service (Arch default) if systemctl list-unit-files | grep -q '^reflector.timer'; then - if systemctl is-enabled reflector.timer >/dev/null 2>&1; then - echo "reflector.timer already enabled" - else - sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer" - fi - if systemctl is-active reflector.timer >/dev/null 2>&1; then - echo "reflector.timer already active" - else - if ! sudo systemctl start reflector.timer; then - echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)" - fi + if systemctl is-enabled reflector.timer > /dev/null 2>&1; then + echo "reflector.timer already enabled" + else + sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer" + fi + if systemctl is-active reflector.timer > /dev/null 2>&1; then + echo "reflector.timer already active" + else + if ! sudo systemctl start reflector.timer; then + echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)" fi + fi elif systemctl list-unit-files | grep -q '^reflector.service'; then - if systemctl is-enabled reflector.service >/dev/null 2>&1; then - echo "reflector.service already enabled" - else - sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service" - fi - if systemctl is-active reflector.service >/dev/null 2>&1; then - echo "reflector.service already running" - else - if ! sudo systemctl start reflector.service; then - echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)" - fi + if systemctl is-enabled reflector.service > /dev/null 2>&1; then + echo "reflector.service already enabled" + else + sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service" + fi + if systemctl is-active reflector.service > /dev/null 2>&1; then + echo "reflector.service already running" + else + if ! sudo systemctl start reflector.service; then + echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)" fi + fi else - echo "reflector systemd unit not found (neither timer nor service)" + echo "reflector systemd unit not found (neither timer nor service)" fi +# Read AUR packages from file (needed before pacman processing) +declare -a aur_packages=() +declare -a aur_package_names=() +while IFS= read -r line; do + if [[ -n $line && $line =~ ^[a-z0-9] ]]; then + aur_packages+=("$line") + aur_package_names+=("${line%% *}") + fi +done < "aur_packages.txt" + # Read pacman packages from file declare -a pacman_packages while IFS= read -r line; do - # Skip empty lines and comments (lines not starting with alphanumeric characters) - if [[ -n "$line" && "$line" =~ ^[a-z0-9] ]]; then - pacman_packages+=("$line") - fi + # Skip empty lines and comments (lines not starting with alphanumeric characters) + if [[ -n $line && $line =~ ^[a-z0-9] ]]; then + pacman_packages+=("$line") + fi done < "pacman_packages.txt" for pkg in "${pacman_packages[@]}"; do - # Skip NVIDIA packages if GPU is not NVIDIA - if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then - echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)" - continue - fi - # Check for texlive subpackages - if [ "$pkg" == "texlive" ]; then - sub_pkgs=( - texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra - texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities - texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience - texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks - texlive-publishers texlive-xetex - ) - all_installed=true - for subpkg in "${sub_pkgs[@]}"; do - if ! pacman -Qi "$subpkg" &> /dev/null; then - all_installed=false - break - fi - done - if [ "$all_installed" = true ]; then - echo "All texlive subpackages are installed, skipping texlive" - continue - fi + # Skip NVIDIA packages if GPU is not NVIDIA + if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then + echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)" + continue + fi + # Check for texlive subpackages + if [ "$pkg" == "texlive" ]; then + sub_pkgs=( + texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra + texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities + texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience + texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks + texlive-publishers texlive-xetex + ) + all_installed=true + for subpkg in "${sub_pkgs[@]}"; do + if ! pacman -Qi "$subpkg" &> /dev/null; then + all_installed=false + break + fi + done + if [ "$all_installed" = true ]; then + echo "All texlive subpackages are installed, skipping texlive" + continue fi + fi - # Check for texlive-lang subpackages - if [ "$pkg" == "texlive-lang" ]; then - sub_pkgs=( - texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic - texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench - texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese - texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese - texlive-langspanish - ) - all_installed=true - for subpkg in "${sub_pkgs[@]}"; do - if ! pacman -Qi "$subpkg" &> /dev/null; then - all_installed=false - break - fi - done - if [ "$all_installed" = true ]; then - echo "All texlive-lang subpackages are installed, skipping texlive-lang" - continue - fi + # Check for texlive-lang subpackages + if [ "$pkg" == "texlive-lang" ]; then + sub_pkgs=( + texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic + texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench + texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese + texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese + texlive-langspanish + ) + all_installed=true + for subpkg in "${sub_pkgs[@]}"; do + if ! pacman -Qi "$subpkg" &> /dev/null; then + all_installed=false + break + fi + done + if [ "$all_installed" = true ]; then + echo "All texlive-lang subpackages are installed, skipping texlive-lang" + continue fi + fi - if ! pacman -Qi "$pkg" &> /dev/null; then - if ! echo "${aur_packages[@]}" | grep -q "$pkg"; then - yes | sudo pacman -Sy --noconfirm "$pkg" - else - echo "$pkg exists in AUR packages, skipping pacman installation" - fi + if ! pacman -Qi "$pkg" &> /dev/null; then + if ! printf '%s +' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then + yes | sudo pacman -Sy --noconfirm "$pkg" else - echo "$pkg is already installed" + echo "$pkg exists in AUR packages, skipping pacman installation" fi + else + echo "$pkg is already installed" + fi done if ! command -v nvm &> /dev/null; then - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash else - echo "nvm is already installed" + echo "nvm is already installed" +fi +export NVM_DIR="$HOME/.nvm" +if [ -s "$NVM_DIR/nvm.sh" ]; then + # shellcheck source=/dev/null + . "$NVM_DIR/nvm.sh" +else + echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2 +fi +if command -v nvm &> /dev/null; then + nvm i v18.20.5 + nvm install --lts +else + echo "nvm command unavailable; skipping Node installation" >&2 fi -export NVM_DIR=$HOME/.nvm; -source $NVM_DIR/nvm.sh; -nvm i v18.20.5 -nvm install --lts sudo systemctl enable bluetooth.service sudo systemctl start bluetooth.service -# Read AUR packages from file -declare -a aur_packages -while IFS= read -r line; do - # Skip empty lines and comments (lines not starting with alphanumeric characters) - if [[ -n "$line" && "$line" =~ ^[a-z0-9] ]]; then - aur_packages+=("$line") - fi -done < "aur_packages.txt" - for entry in "${aur_packages[@]}"; do - pkg_name=$(echo "$entry" | cut -d' ' -f1) - repo_url=$(echo "$entry" | cut -d' ' -f2) - install_from_aur "$repo_url" "$pkg_name" + pkg_name=${entry%% *} + repo_url=${entry#* } + if [ "$repo_url" = "$pkg_name" ] || [ -z "$repo_url" ]; then + repo_url="https://aur.archlinux.org/${pkg_name}.git" + fi + install_from_aur "$repo_url" "$pkg_name" done cd ~/linux-configuration/fresh-install if [ ! -d "$HOME/.config/mpv" ]; then - mkdir -p "$HOME/.config/mpv" + mkdir -p "$HOME/.config/mpv" fi cp mpv.conf "$HOME/.config/mpv/mpv.conf" if [ ! -d "$HOME/.oh-my-zsh" ]; then - yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" else - echo "Oh My Zsh is already installed" + echo "Oh My Zsh is already installed" fi cd ~/linux-configuration sudo hosts/install.sh i3-configuration/install.sh scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh -scripts/fixes/nvidia_troubleshoot.sh -sudo scripts/features/setup_activitywatch.sh +scripts/fixes/nvidia_troubleshoot.sh +sudo scripts/features/setup_activitywatch.sh sudo scripts/utils/setup_media_organizer.sh sudo scripts/digital_wellbeing/setup_pc_startup_monitor.sh -yes | sudo scripts/setup_periodic_system.sh +yes | sudo scripts/setup_periodic_system.sh sudo scripts/setup_thorium_startup.sh yes | protonup -yes | sudo pacman -Syuu +yes | sudo pacman -Syuu #cd unreal-engine ## gh auth login diff --git a/fresh-install/makepkg.conf b/fresh-install/makepkg.conf index ac4c95f..e16580c 100644 --- a/fresh-install/makepkg.conf +++ b/fresh-install/makepkg.conf @@ -12,11 +12,11 @@ #-- The download utilities that makepkg should use to acquire sources # Format: 'protocol::agent' DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u' - 'ftp::/usr/bin/curl -qgfC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u' - 'http::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' - 'https::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' - 'rsync::/usr/bin/rsync --no-motd -z %u %o' - 'scp::/usr/bin/scp -C %u %o') + 'ftp::/usr/bin/curl -qgfC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u' + 'http::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' + 'https::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' + 'rsync::/usr/bin/rsync --no-motd -z %u %o' + 'scp::/usr/bin/scp -C %u %o') # Other common tools: # /usr/bin/snarf @@ -26,10 +26,10 @@ DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u' #-- The package required by makepkg to download VCS sources # Format: 'protocol::package' VCSCLIENTS=('bzr::breezy' - 'fossil::fossil' - 'git::git' - 'hg::mercurial' - 'svn::subversion') + 'fossil::fossil' + 'git::git' + 'hg::mercurial' + 'svn::subversion') ######################################################################### # ARCHITECTURE, COMPILE FLAGS diff --git a/hosts/guard/enforce-hosts.sh b/hosts/guard/enforce-hosts.sh index ba4e4a0..2eba90a 100755 --- a/hosts/guard/enforce-hosts.sh +++ b/hosts/guard/enforce-hosts.sh @@ -9,24 +9,24 @@ TARGET="/etc/hosts" LOG_FILE="/var/log/hosts-guard.log" log() { - printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2 + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2 } -if [[ ! -f "$CANONICAL_SOURCE" ]]; then - log "Canonical hosts not found at $CANONICAL_SOURCE; aborting enforcement" - exit 0 +if [[ ! -f $CANONICAL_SOURCE ]]; then + log "Canonical hosts not found at $CANONICAL_SOURCE; aborting enforcement" + exit 0 fi if ! cmp -s "$CANONICAL_SOURCE" "$TARGET"; then - log "Difference detected – restoring $TARGET from canonical copy" - cp "$CANONICAL_SOURCE" "$TARGET" - chmod 644 "$TARGET" + log "Difference detected – restoring $TARGET from canonical copy" + cp "$CANONICAL_SOURCE" "$TARGET" + chmod 644 "$TARGET" else - log "No drift detected (contents identical)" + log "No drift detected (contents identical)" fi # Re-apply protective attributes: immutable first, then read-only bind mount handled by separate unit -chattr -i -a "$TARGET" 2>/dev/null || true +chattr -i -a "$TARGET" 2> /dev/null || true chattr +i "$TARGET" || log "Failed to set immutable attribute" -log "Enforcement complete" \ No newline at end of file +log "Enforcement complete" diff --git a/hosts/guard/install_pacman_hooks.sh b/hosts/guard/install_pacman_hooks.sh index c4b63c5..4a1169c 100755 --- a/hosts/guard/install_pacman_hooks.sh +++ b/hosts/guard/install_pacman_hooks.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi } +require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi; } require_root "$@" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -10,7 +10,7 @@ HOOKS_DIR="/etc/pacman.d/hooks" install -d -m 755 "$HOOKS_DIR" # Pre-transaction hook -cat >"$HOOKS_DIR/10-unlock-etc-hosts.hook" <<'HOOK' +cat > "$HOOKS_DIR/10-unlock-etc-hosts.hook" << 'HOOK' [Trigger] Operation = Upgrade Operation = Install @@ -26,7 +26,7 @@ NeedsTargets HOOK # Post-transaction hook -cat >"$HOOKS_DIR/90-relock-etc-hosts.hook" <<'HOOK' +cat > "$HOOKS_DIR/90-relock-etc-hosts.hook" << 'HOOK' [Trigger] Operation = Upgrade Operation = Install diff --git a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh index 450f4e7..e2f2a8b 100644 --- a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh +++ b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh @@ -5,22 +5,22 @@ TARGET=/etc/hosts ENFORCE=/usr/local/sbin/enforce-hosts.sh LOGTAG=hosts-guard-hook -mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } +mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2> /dev/null || echo 0; } collapse_mounts() { local i=0 - if command -v mountpoint >/devnull 2>&1; then + if command -v mountpoint > /devnull 2>&1; then while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break + umount -l "$TARGET" > /dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break done else local cnt cnt=$(mount_layers_count) - while (( cnt > 1 )); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break + while ((cnt > 1)); do + umount -l "$TARGET" > /dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break cnt=$(mount_layers_count) done fi @@ -28,23 +28,23 @@ collapse_mounts() { # Ensure we end with a single read-only bind mount layer logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)" -echo "$(date -Is) post-relock(start)" >> /run/hosts-guard-hook.log 2>/dev/null || true +echo "$(date -Is) post-relock(start)" >> /run/hosts-guard-hook.log 2> /dev/null || true collapse_mounts -if [[ -x "$ENFORCE" ]]; then - "$ENFORCE" >/dev/null 2>&1 || true +if [[ -x $ENFORCE ]]; then + "$ENFORCE" > /dev/null 2>&1 || true fi # Apply exactly one ro bind layer -mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true -mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true +mount --bind "$TARGET" "$TARGET" > /dev/null 2>&1 || true +mount -o remount,ro,bind "$TARGET" > /dev/null 2>&1 || true # Start only the path watcher; avoid bind-mount service (we already bound once) -if command -v systemctl >/dev/null 2>&1; then - systemctl start hosts-guard.path >/dev/null 2>&1 || true +if command -v systemctl > /dev/null 2>&1; then + systemctl start hosts-guard.path > /dev/null 2>&1 || true fi logger -t "$LOGTAG" "post: relocking /etc/hosts (done)" -echo "$(date -Is) post-relock(done)" >> /run/hosts-guard-hook.log 2>/dev/null || true +echo "$(date -Is) post-relock(done)" >> /run/hosts-guard-hook.log 2> /dev/null || true exit 0 diff --git a/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh index e44866a..8edd96b 100644 --- a/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh +++ b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh @@ -7,58 +7,57 @@ LOGTAG=hosts-guard-hook stop_units_if_present() { local units=(hosts-bind-mount.service hosts-guard.path) for u in "${units[@]}"; do - if command -v systemctl >/dev/null 2>&1; then - if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then - systemctl stop "$u" >/dev/null 2>&1 || true + if command -v systemctl > /dev/null 2>&1; then + if systemctl list-unit-files 2> /dev/null | grep -q "^$u"; then + systemctl stop "$u" > /dev/null 2>&1 || true fi fi done } -is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; } -is_bind_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw bind; } +is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2> /dev/null | grep -qw ro; } -mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } +mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2> /dev/null || echo 0; } cleanup_mount_stacks() { local i=0 # Unmount bind layers until /etc/hosts is no longer a mountpoint - if command -v mountpoint >/dev/null 2>&1; then + if command -v mountpoint > /dev/null 2>&1; then while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break + umount -l "$TARGET" > /dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break done else # Fallback to best-effort using mountinfo count local cnt cnt=$(mount_layers_count) - while (( cnt > 1 )); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break + while ((cnt > 1)); do + umount -l "$TARGET" > /dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break cnt=$(mount_layers_count) done fi } # Drop protective attributes if present -if command -v lsattr >/dev/null 2>&1; then - attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) - echo "$attrs" | grep -q " i " && chattr -i "$TARGET" >/dev/null 2>&1 || true - echo "$attrs" | grep -q " a " && chattr -a "$TARGET" >/dev/null 2>&1 || true +if command -v lsattr > /dev/null 2>&1; then + attrs=$(lsattr -d "$TARGET" 2> /dev/null || true) + echo "$attrs" | grep -q " i " && chattr -i "$TARGET" > /dev/null 2>&1 || true + echo "$attrs" | grep -q " a " && chattr -a "$TARGET" > /dev/null 2>&1 || true fi stop_units_if_present logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)" -echo "$(date -Is) pre-unlock" >> /run/hosts-guard-hook.log 2>/dev/null || true +echo "$(date -Is) pre-unlock" >> /run/hosts-guard-hook.log 2> /dev/null || true # Always collapse any existing layers; we'll operate on the plain file cleanup_mount_stacks # If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again if is_ro_mount; then - mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks + mount -o remount,rw "$TARGET" > /dev/null 2>&1 || cleanup_mount_stacks fi logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)" diff --git a/hosts/guard/psychological/unlock-hosts.sh b/hosts/guard/psychological/unlock-hosts.sh index 4b82faf..959ae0e 100644 --- a/hosts/guard/psychological/unlock-hosts.sh +++ b/hosts/guard/psychological/unlock-hosts.sh @@ -11,36 +11,36 @@ DELAY_SECONDS=45 log() { printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG" >&2; } -require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi } +require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi; } require_root "$@" echo "Reason for editing /etc/hosts (will be logged):" >&2 read -r -p "Enter reason: " REASON -if [[ -z ${REASON// } ]]; then - echo "Empty reason not allowed. Aborting." >&2 - exit 1 +if [[ -z ${REASON// /} ]]; then + echo "Empty reason not allowed. Aborting." >&2 + exit 1 fi log "Requested intentional /etc/hosts modification session. Reason: $REASON" logger -t "$SYSLOG_TAG" "session_start user=${SUDO_USER:-$USER} reason='$REASON'" echo "This action is logged. A cooling-off delay of $DELAY_SECONDS seconds applies." >&2 for s in hosts-bind-mount.service hosts-guard.path; do - if systemctl is-active --quiet "$s"; then - log "Stopping $s" - systemctl stop "$s" || true - fi - if systemctl is-enabled --quiet "$s"; then - log "(Will re-enable later)" - fi + if systemctl is-active --quiet "$s"; then + log "Stopping $s" + systemctl stop "$s" || true + fi + if systemctl is-enabled --quiet "$s"; then + log "(Will re-enable later)" + fi done # Remove attributes to allow edit -chattr -i -a "$TARGET" 2>/dev/null || true +chattr -i -a "$TARGET" 2> /dev/null || true echo "Countdown:" >&2 -for ((i=DELAY_SECONDS; i>0; i--)); do - printf '\rEdit window opens in %2d seconds... Press Ctrl+C to abort.' "$i" >&2 - sleep 1 +for ((i = DELAY_SECONDS; i > 0; i--)); do + printf '\rEdit window opens in %2d seconds... Press Ctrl+C to abort.' "$i" >&2 + sleep 1 done echo >&2 @@ -49,13 +49,13 @@ sha_before=$(sha256sum "$TARGET" | awk '{print $1}') "$EDITOR_CMD" "$TARGET" sha_after=$(sha256sum "$TARGET" | awk '{print $1}') -if [[ "$sha_before" == "$sha_after" ]]; then - log "No changes made to $TARGET. Reason: $REASON" - logger -t "$SYSLOG_TAG" "no_change user=${SUDO_USER:-$USER} reason='$REASON'" +if [[ $sha_before == "$sha_after" ]]; then + log "No changes made to $TARGET. Reason: $REASON" + logger -t "$SYSLOG_TAG" "no_change user=${SUDO_USER:-$USER} reason='$REASON'" else - log "Changes detected. Updating canonical copy and re-enforcing. Reason: $REASON" - logger -t "$SYSLOG_TAG" "modified user=${SUDO_USER:-$USER} reason='$REASON'" - cp "$TARGET" "$CANON" + log "Changes detected. Updating canonical copy and re-enforcing. Reason: $REASON" + logger -t "$SYSLOG_TAG" "modified user=${SUDO_USER:-$USER} reason='$REASON'" + cp "$TARGET" "$CANON" fi # Re-run enforcement diff --git a/hosts/guard/setup_hosts_guard.sh b/hosts/guard/setup_hosts_guard.sh index 7da6cdc..19da91c 100755 --- a/hosts/guard/setup_hosts_guard.sh +++ b/hosts/guard/setup_hosts_guard.sh @@ -46,10 +46,20 @@ ADD_ALIAS_STUB=1 msg() { printf '\e[1;32m[+]\e[0m %s\n' "$*"; } note() { printf '\e[1;34m[i]\e[0m %s\n' "$*"; } warn() { printf '\e[1;33m[!]\e[0m %s\n' "$*"; } -err() { printf '\e[1;31m[x]\e[0m %s\n' "$*" >&2; } -run() { if [[ $DRY_RUN -eq 1 ]]; then printf 'DRY-RUN: %s\n' "$*"; else eval "$@"; fi } +err() { printf '\e[1;31m[x]\e[0m %s\n' "$*" >&2; } +run() { + if [[ $DRY_RUN -eq 1 ]]; then + printf 'DRY-RUN:' + if [ "$#" -gt 0 ]; then + printf ' %q' "$@" + fi + printf '\n' + else + "$@" + fi +} -require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi } +require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi; } usage() { sed -n '1,/^set -euo pipefail/p' "$0" | sed 's/^# \{0,1\}//'; } @@ -58,21 +68,71 @@ usage() { sed -n '1,/^set -euo pipefail/p' "$0" | sed 's/^# \{0,1\}//'; } ###################################################################### while [[ $# -gt 0 ]]; do case "$1" in - --force-snapshot) FORCE_SNAPSHOT=1 ; shift ;; - --no-snapshot) DO_SNAPSHOT=0 ; shift ;; - --skip-bind) ENABLE_BIND=0 ; shift ;; - --skip-path-watch) ENABLE_PATH=0 ; shift ;; - --delay) DELAY=${2:-} ; [[ -z ${DELAY} ]] && { err '--delay requires value'; exit 2; } ; shift 2 ;; - --dry-run) DRY_RUN=1 ; shift ;; - --no-shell-hooks) INSTALL_SHELL_HOOKS=0 ; shift ;; - --shell-hooks) INSTALL_SHELL_HOOKS=1 ; shift ;; - --no-audit) INSTALL_AUDIT_RULE=0 ; shift ;; - --audit) INSTALL_AUDIT_RULE=1 ; shift ;; - --no-alias-stub) ADD_ALIAS_STUB=0 ; shift ;; - --alias-stub) ADD_ALIAS_STUB=1 ; shift ;; - --uninstall) UNINSTALL=1 ; shift ;; - -h|--help) usage; exit 0 ;; - *) err "Unknown argument: $1"; usage; exit 2 ;; + --force-snapshot) + FORCE_SNAPSHOT=1 + shift + ;; + --no-snapshot) + DO_SNAPSHOT=0 + shift + ;; + --skip-bind) + ENABLE_BIND=0 + shift + ;; + --skip-path-watch) + ENABLE_PATH=0 + shift + ;; + --delay) + DELAY=${2:-} + [[ -z ${DELAY} ]] && { + err '--delay requires value' + exit 2 + } + shift 2 + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + --no-shell-hooks) + INSTALL_SHELL_HOOKS=0 + shift + ;; + --shell-hooks) + INSTALL_SHELL_HOOKS=1 + shift + ;; + --no-audit) + INSTALL_AUDIT_RULE=0 + shift + ;; + --audit) + INSTALL_AUDIT_RULE=1 + shift + ;; + --no-alias-stub) + ADD_ALIAS_STUB=0 + shift + ;; + --alias-stub) + ADD_ALIAS_STUB=1 + shift + ;; + --uninstall) + UNINSTALL=1 + shift + ;; + -h | --help) + usage + exit 0 + ;; + *) + err "Unknown argument: $1" + usage + exit 2 + ;; esac done @@ -119,7 +179,7 @@ if [[ $UNINSTALL -eq 1 ]]; then "$SYSTEMD_DIR/hosts-bind-mount.service" \ "$ZSH_FILTER_SNIPPET" \ "$BASH_FILTER_SNIPPET"; do - if [[ -e $f ]]; then run rm -f "$f"; fi + if [[ -e $f ]]; then run rm -f "$f"; fi done note "Leaving canonical snapshot at $CANON (remove manually if undesired)." if [[ $DRY_RUN -eq 0 ]]; then systemctl daemon-reload; fi @@ -134,10 +194,13 @@ note "Script directory: $SCRIPT_DIR" note "Repository root: $REPO_ROOT" for req in "$TEMPLATE_ENFORCE" "$TEMPLATE_UNLOCK" "$UNIT_GUARD_SERVICE"; do - [[ -f $req ]] || { err "Missing template: $req"; exit 1; } + [[ -f $req ]] || { + err "Missing template: $req" + exit 1 + } done -if [[ ! -f "$HOSTS" ]]; then +if [[ ! -f $HOSTS ]]; then err "$HOSTS does not exist. Run your hosts/install.sh first." exit 1 fi @@ -146,7 +209,7 @@ fi # Snapshot ###################################################################### if [[ $DO_SNAPSHOT -eq 1 ]]; then - if [[ -f "$CANON" && $FORCE_SNAPSHOT -eq 0 ]]; then + if [[ -f $CANON && $FORCE_SNAPSHOT -eq 0 ]]; then note "Canonical snapshot exists (use --force-snapshot to overwrite)" else msg "Creating canonical snapshot at $CANON" @@ -182,14 +245,12 @@ fi if [[ $INSTALL_SHELL_HOOKS -eq 1 ]]; then msg "Installing shell history suppression hooks for unlock command" # Pattern matches commands invoking unlock-hosts (with or without sudo) & setup script force snapshot - FILTER_PATTERN='(^|;|&&|\|\|)\s*(sudo\s+)?(/usr/local/sbin/)?unlock-hosts(\s|;|$)' - # Zsh: use zshaddhistory function - if command -v zsh >/dev/null 2>&1; then + if command -v zsh > /dev/null 2>&1; then if [[ $DRY_RUN -eq 1 ]]; then echo "DRY-RUN: would create $ZSH_FILTER_SNIPPET" else - cat > "$ZSH_FILTER_SNIPPET" <<'ZEOF' + cat > "$ZSH_FILTER_SNIPPET" << 'ZEOF' # Added by hosts guard setup – suppress unlock-hosts commands from Zsh history autoload -Uz add-zsh-hook 2>/dev/null || true _hosts_guard_history_filter() { @@ -213,11 +274,11 @@ ZEOF fi # Bash: rely on HISTCONTROL and PROMPT_COMMAND filter - if command -v bash >/dev/null 2>&1; then + if command -v bash > /dev/null 2>&1; then if [[ $DRY_RUN -eq 1 ]]; then echo "DRY-RUN: would create $BASH_FILTER_SNIPPET" else - cat > "$BASH_FILTER_SNIPPET" <<'BEOF' + cat > "$BASH_FILTER_SNIPPET" << 'BEOF' # Added by hosts guard setup – suppress unlock-hosts commands from Bash history export HISTCONTROL=ignoredups:erasedups _hosts_guard_hist_filter() { @@ -253,7 +314,7 @@ if [[ $ADD_ALIAS_STUB -eq 1 ]]; then if [[ $DRY_RUN -eq 1 ]]; then echo "DRY-RUN: would create $PROFILE_STUB" else - cat > "$PROFILE_STUB" <<'ASTUB' + cat > "$PROFILE_STUB" << 'ASTUB' # Added by hosts guard setup – discourages casual use of unlock-hosts name if command -v unlock-hosts >/dev/null 2>&1; then alias unlock-hosts='command_not_found_handle 2>/dev/null || echo "Use: sudo /usr/local/sbin/unlock-hosts (logged & delayed)"' @@ -267,16 +328,17 @@ fi # Audit rule to record executions (requires auditd) ###################################################################### if [[ $INSTALL_AUDIT_RULE -eq 1 ]]; then - if command -v auditctl >/dev/null 2>&1; then - AUDIT_RULE="-w /usr/local/sbin/unlock-hosts -p x -k hosts_unlock" - if auditctl -l 2>/dev/null | grep -Fq "/usr/local/sbin/unlock-hosts"; then + if command -v auditctl > /dev/null 2>&1; then + audit_rule_str="-w /usr/local/sbin/unlock-hosts -p x -k hosts_unlock" + audit_rule_args=(-w /usr/local/sbin/unlock-hosts -p x -k hosts_unlock) + if auditctl -l 2> /dev/null | grep -Fq "/usr/local/sbin/unlock-hosts"; then note "Audit rule already present" else - run auditctl $AUDIT_RULE || warn "Failed to add audit rule (runtime)" + run auditctl "${audit_rule_args[@]}" || warn "Failed to add audit rule (runtime)" if [[ $DRY_RUN -eq 1 ]]; then echo "DRY-RUN: would create /etc/audit/rules.d/hosts_unlock.rules" else - echo "$AUDIT_RULE" > /etc/audit/rules.d/hosts_unlock.rules + echo "$audit_rule_str" > /etc/audit/rules.d/hosts_unlock.rules fi fi else @@ -323,22 +385,22 @@ fi ###################################################################### echo msg "Hosts guard setup complete" -echo "Canonical copy: $CANON" -echo "Enforce script: $INSTALL_ENFORCE" -echo "Unlock command: sudo $INSTALL_UNLOCK" -echo "Delay (seconds): $DELAY" -echo "Auto-revert path watch: $([[ $ENABLE_PATH -eq 1 ]] && echo enabled || echo disabled)" -echo "Read-only bind mount: $([[ $ENABLE_BIND -eq 1 ]] && echo enabled || echo disabled)" +echo "Canonical copy: $CANON" +echo "Enforce script: $INSTALL_ENFORCE" +echo "Unlock command: sudo $INSTALL_UNLOCK" +echo "Delay (seconds): $DELAY" +echo "Auto-revert path watch: $([[ $ENABLE_PATH -eq 1 ]] && echo enabled || echo disabled)" +echo "Read-only bind mount: $([[ $ENABLE_BIND -eq 1 ]] && echo enabled || echo disabled)" echo "Shell history suppression: $([[ $INSTALL_SHELL_HOOKS -eq 1 ]] && echo enabled || echo disabled)" echo "Audit rule: $([[ $INSTALL_AUDIT_RULE -eq 1 ]] && echo enabled || echo disabled)" echo "Alias stub: $([[ $ADD_ALIAS_STUB -eq 1 ]] && echo enabled || echo disabled)" echo -echo "Test flow:" -echo " sudo sed -i '1s/.*/# tamper test/' /etc/hosts # Should revert automatically" -echo " sudo $INSTALL_UNLOCK # Intentional edit workflow" +echo "Test flow:" +echo " sudo sed -i '1s/.*/# tamper test/' /etc/hosts # Should revert automatically" +echo " sudo $INSTALL_UNLOCK # Intentional edit workflow" echo -echo "Uninstall:" -echo " sudo $0 --uninstall" -echo "(Optional) Skip shell history hooks: --no-shell-hooks" +echo "Uninstall:" +echo " sudo $0 --uninstall" +echo "(Optional) Skip shell history hooks: --no-shell-hooks" echo exit 0 diff --git a/hosts/install.sh b/hosts/install.sh index 8624cf5..eaa0035 100755 --- a/hosts/install.sh +++ b/hosts/install.sh @@ -2,7 +2,7 @@ # Re-run with sudo if not root if [[ $EUID -ne 0 ]]; then - exec sudo -E bash "$0" "$@" + exec sudo -E bash "$0" "$@" fi # Options @@ -11,25 +11,25 @@ FLUSH_DNS=0 # Parse CLI flags for arg in "$@"; do - case "$arg" in - --flush-dns) - FLUSH_DNS=1 - ;; - --no-flush-dns) - FLUSH_DNS=0 - ;; - -h|--help) - echo "Usage: $0 [--flush-dns|--no-flush-dns]" - exit 0 - ;; - esac + case "$arg" in + --flush-dns) + FLUSH_DNS=1 + ;; + --no-flush-dns) + FLUSH_DNS=0 + ;; + -h | --help) + echo "Usage: $0 [--flush-dns|--no-flush-dns]" + exit 0 + ;; + esac done # Enable systemd-resolved sudo systemctl enable systemd-resolved # Remove all attributes from /etc/hosts to allow modifications -sudo chattr -i -a /etc/hosts 2>/dev/null || true +sudo chattr -i -a /etc/hosts 2> /dev/null || true # Source and local cache configuration URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts" @@ -38,33 +38,33 @@ LOCAL_CACHE="/etc/hosts.stevenblack" # Helpers extract_date_epoch_from_file() { - # Grep "# Date:" line and convert to epoch seconds (UTC) - local f="$1" - local line - line=$(grep -m1 '^# Date:' "$f" 2>/dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/') - if [[ -n "$line" ]]; then - date -u -d "$line" +%s 2>/dev/null || echo "" - else - echo "" - fi + # Grep "# Date:" line and convert to epoch seconds (UTC) + local f="$1" + local line + line=$(grep -m1 '^# Date:' "$f" 2> /dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/') + if [[ -n $line ]]; then + date -u -d "$line" +%s 2> /dev/null || echo "" + else + echo "" + fi } fetch_remote_header() { - # Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head - local out="$1" - if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then - return 0 - fi - # Fallback – may download more, but we only keep first lines - if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then - return 0 - fi - return 1 + # Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head + local out="$1" + if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then + return 0 + fi + # Fallback – may download more, but we only keep first lines + if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then + return 0 + fi + return 1 } download_remote_full_to() { - local out="$1" - curl -LfsS "$URL" -o "$out" + local out="$1" + curl -LfsS "$URL" -o "$out" } # Decide whether to use cache or update @@ -73,50 +73,47 @@ trap 'rm -f "$TMP_REMOTE_HEAD"' EXIT REMOTE_AVAILABLE=0 if fetch_remote_header "$TMP_REMOTE_HEAD"; then - REMOTE_AVAILABLE=1 + REMOTE_AVAILABLE=1 fi -USE_CACHE=0 NEED_UPDATE=0 -if [[ -f "$LOCAL_CACHE" ]]; then - local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE") +if [[ -f $LOCAL_CACHE ]]; then + local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE") else - local_epoch="" + local_epoch="" fi if [[ $REMOTE_AVAILABLE -eq 1 ]]; then - remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD") - if [[ -n "$local_epoch" && -n "$remote_epoch" && "$local_epoch" -ge "$remote_epoch" ]]; then - echo "Using cached StevenBlack hosts (up-to-date)." - USE_CACHE=1 - else - echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..." - NEED_UPDATE=1 - fi + remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD") + if [[ -n $local_epoch && -n $remote_epoch && $local_epoch -ge $remote_epoch ]]; then + echo "Using cached StevenBlack hosts (up-to-date)." + else + echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..." + NEED_UPDATE=1 + fi else - if [[ -f "$LOCAL_CACHE" ]]; then - echo "No internet; using cached StevenBlack hosts." - USE_CACHE=1 - else - echo "Error: No internet and no cached StevenBlack hosts found." >&2 - exit 1 - fi + if [[ -f $LOCAL_CACHE ]]; then + echo "No internet; using cached StevenBlack hosts." + else + echo "Error: No internet and no cached StevenBlack hosts found." >&2 + exit 1 + fi fi # Ensure we have a fresh cache if needed if [[ $NEED_UPDATE -eq 1 ]]; then - TMP_DL=$(mktemp) - if download_remote_full_to "$TMP_DL"; then - # Save raw upstream to cache - sudo mv "$TMP_DL" "$LOCAL_CACHE" - sudo chmod 644 "$LOCAL_CACHE" - echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE" - else - rm -f "$TMP_DL" - echo "Error: Failed to download latest StevenBlack hosts." >&2 - exit 1 - fi + TMP_DL=$(mktemp) + if download_remote_full_to "$TMP_DL"; then + # Save raw upstream to cache + sudo mv "$TMP_DL" "$LOCAL_CACHE" + sudo chmod 644 "$LOCAL_CACHE" + echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE" + else + rm -f "$TMP_DL" + echo "Error: Failed to download latest StevenBlack hosts." >&2 + exit 1 + fi fi # Install the base hosts from cache into /etc/hosts @@ -265,10 +262,10 @@ sudo chattr -i /etc/hosts sudo chattr +a /etc/hosts # Optionally flush DNS caches -if [[ "$FLUSH_DNS" -eq 1 ]]; then - echo "Flushing DNS caches..." - sudo systemd-resolve --flush-caches - sudo systemctl restart NetworkManager.service +if [[ $FLUSH_DNS -eq 1 ]]; then + echo "Flushing DNS caches..." + sudo systemd-resolve --flush-caches + sudo systemctl restart NetworkManager.service else - echo "DNS cache flush skipped (use --flush-dns to enable)." -fi \ No newline at end of file + echo "DNS cache flush skipped (use --flush-dns to enable)." +fi diff --git a/i3-configuration/i3blocks/activitywatch_status.sh b/i3-configuration/i3blocks/activitywatch_status.sh index ecb9cba..9fa9efe 100755 --- a/i3-configuration/i3blocks/activitywatch_status.sh +++ b/i3-configuration/i3blocks/activitywatch_status.sh @@ -4,45 +4,45 @@ # Check if ActivityWatch is installed check_installed() { - # Check if activitywatch-bin package is installed - if pacman -Qi activitywatch-bin &>/dev/null; then - return 0 - fi - - # Check if aw-qt binary exists - if command -v aw-qt &>/dev/null; then - return 0 - fi - - return 1 + # Check if activitywatch-bin package is installed + if pacman -Qi activitywatch-bin &> /dev/null; then + return 0 + fi + + # Check if aw-qt binary exists + if command -v aw-qt &> /dev/null; then + return 0 + fi + + return 1 } # Check if ActivityWatch is running check_running() { - # Check for aw-qt process - if pgrep -f "aw-qt" >/dev/null 2>&1; then - return 0 - fi - - # Check for aw-server process - if pgrep -f "aw-server" >/dev/null 2>&1; then - return 0 - fi - - return 1 + # Check for aw-qt process + if pgrep -f "aw-qt" > /dev/null 2>&1; then + return 0 + fi + + # Check for aw-server process + if pgrep -f "aw-server" > /dev/null 2>&1; then + return 0 + fi + + return 1 } # Main logic if ! check_installed; then - echo "AW uninstalled" - echo - echo "#FF0000" # Red + echo "AW uninstalled" + echo + echo "#FF0000" # Red elif check_running; then - echo "AW on" - echo - echo "#00FF00" # Green + echo "AW on" + echo + echo "#00FF00" # Green else - echo "AW off" - echo - echo "#FF0000" # Red + echo "AW off" + echo + echo "#FF0000" # Red fi diff --git a/i3-configuration/i3blocks/battery_status.sh b/i3-configuration/i3blocks/battery_status.sh index bba1a05..8d58b91 100755 --- a/i3-configuration/i3blocks/battery_status.sh +++ b/i3-configuration/i3blocks/battery_status.sh @@ -8,4 +8,4 @@ acpi -b | awk -F', ' ' if (time[1] != "") printf ", %s", time[1] if ($1 ~ /Charging/) printf ", " printf "\n" - }' \ No newline at end of file + }' diff --git a/i3-configuration/i3blocks/bluetooth.sh b/i3-configuration/i3blocks/bluetooth.sh index 02af983..bdd9bb8 100755 --- a/i3-configuration/i3blocks/bluetooth.sh +++ b/i3-configuration/i3blocks/bluetooth.sh @@ -5,11 +5,10 @@ bluetooth_info=$(bluetoothctl info) # Check if Bluetooth is connected if echo "$bluetooth_info" | grep -q "Connected: yes"; then - device=$(echo "$bluetooth_info" | grep "Alias" | cut -d ' ' -f2-) - echo " $device" #  is the Bluetooth icon - echo - echo "#50FA7B" # Green for connected + device=$(echo "$bluetooth_info" | grep "Alias" | cut -d ' ' -f2-) + echo " $device" #  is the Bluetooth icon + echo + echo "#50FA7B" # Green for connected else - echo " Disconnected" + echo " Disconnected" fi - diff --git a/i3-configuration/i3blocks/cpu_monitor.sh b/i3-configuration/i3blocks/cpu_monitor.sh index c720dfa..f2b7edc 100755 --- a/i3-configuration/i3blocks/cpu_monitor.sh +++ b/i3-configuration/i3blocks/cpu_monitor.sh @@ -3,46 +3,46 @@ # CPU Temperature cpu_temp=$(sensors | awk '/^Tctl:/ {print $2}' | tr -d '+°C') if [ -z "$cpu_temp" ]; then - cpu_temp=$(sensors | awk '/^Package id 0:/ {print $4}' | tr -d '+°C') + cpu_temp=$(sensors | awk '/^Package id 0:/ {print $4}' | tr -d '+°C') fi if [ -z "$cpu_temp" ]; then - cpu_temp=$(sensors | awk '/^Core 0:/ {print $3}' | tr -d '+°C') + cpu_temp=$(sensors | awk '/^Core 0:/ {print $3}' | tr -d '+°C') fi if [ -z "$cpu_temp" ]; then - cpu_temp="N/A" + cpu_temp="N/A" fi # CPU Load (1-minute average) cpu_load=$(awk '{print $1}' /proc/loadavg) if [ -z "$cpu_load" ]; then - cpu_load="N/A" + cpu_load="N/A" fi # Colors for CPU Load and Temperature -cpu_color="#FFFFFF" # Default color +cpu_color="#FFFFFF" # Default color # Change color based on CPU load -if [[ "$cpu_load" != "N/A" ]]; then - cpu_load_float=$(echo "$cpu_load" | awk '{print ($1 + 0)}') - if (( $(echo "$cpu_load_float < 1.0" | bc -l) )); then - cpu_color="#50FA7B" # Green for low load - elif (( $(echo "$cpu_load_float < 2.0" | bc -l) )); then - cpu_color="#F1FA8C" # Yellow for medium load - else - cpu_color="#FF5555" # Red for high load - fi +if [[ $cpu_load != "N/A" ]]; then + cpu_load_float=$(echo "$cpu_load" | awk '{print ($1 + 0)}') + if (($(echo "$cpu_load_float < 1.0" | bc -l))); then + cpu_color="#50FA7B" # Green for low load + elif (($(echo "$cpu_load_float < 2.0" | bc -l))); then + cpu_color="#F1FA8C" # Yellow for medium load + else + cpu_color="#FF5555" # Red for high load + fi fi # Change color based on CPU temperature -if [[ "$cpu_temp" != "N/A" ]]; then - cpu_temp_float=$(echo "$cpu_temp" | awk '{print ($1 + 0)}') - if (( $(echo "$cpu_temp_float < 65.0" | bc -l) )); then - cpu_color="#50FA7B" # Green for low temperature - elif (( $(echo "$cpu_temp_float < 85.0" | bc -l) )); then - cpu_color="#F1FA8C" # Yellow for medium temperature - else - cpu_color="#FF5555" # Red for high temperature - fi +if [[ $cpu_temp != "N/A" ]]; then + cpu_temp_float=$(echo "$cpu_temp" | awk '{print ($1 + 0)}') + if (($(echo "$cpu_temp_float < 65.0" | bc -l))); then + cpu_color="#50FA7B" # Green for low temperature + elif (($(echo "$cpu_temp_float < 85.0" | bc -l))); then + cpu_color="#F1FA8C" # Yellow for medium temperature + else + cpu_color="#FF5555" # Red for high temperature + fi fi -echo -e " ${cpu_temp}°C, ${cpu_load}" \ No newline at end of file +echo -e " ${cpu_temp}°C, ${cpu_load}" diff --git a/i3-configuration/i3blocks/gpu_monitor.sh b/i3-configuration/i3blocks/gpu_monitor.sh index 412e079..cfe1c8b 100755 --- a/i3-configuration/i3blocks/gpu_monitor.sh +++ b/i3-configuration/i3blocks/gpu_monitor.sh @@ -2,41 +2,41 @@ # Function to get NVIDIA GPU metrics get_nvidia_metrics() { - gpu_temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null) - if [ -z "$gpu_temp" ]; then - gpu_temp="N/A" - fi + gpu_temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2> /dev/null) + if [ -z "$gpu_temp" ]; then + gpu_temp="N/A" + fi - gpu_load=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null) - if [ -z "$gpu_load" ]; then - gpu_load="N/A" - fi + gpu_load=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2> /dev/null) + if [ -z "$gpu_load" ]; then + gpu_load="N/A" + fi - echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" + echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" } # Function to get Intel GPU metrics get_intel_metrics() { - gpu_load=$(cat /sys/class/drm/card0/device/gpu_busy_percent 2>/dev/null) - if [ -z "$gpu_load" ]; then - gpu_load="N/A" - fi + gpu_load=$(cat /sys/class/drm/card0/device/gpu_busy_percent 2> /dev/null) + if [ -z "$gpu_load" ]; then + gpu_load="N/A" + fi - gpu_temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C') - if [ -z "$gpu_temp" ]; then - gpu_temp="N/A" - fi + gpu_temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C') + if [ -z "$gpu_temp" ]; then + gpu_temp="N/A" + fi - echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" + echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" } # Detect GPU type and get metrics if lspci | grep -i nvidia > /dev/null; then - gpu_metrics=$(get_nvidia_metrics) + gpu_metrics=$(get_nvidia_metrics) elif lspci | grep -i vga | grep -i intel > /dev/null; then - gpu_metrics=$(get_intel_metrics) + gpu_metrics=$(get_intel_metrics) else - echo "No supported GPU found." + echo "No supported GPU found." fi #!/bin/bash @@ -44,22 +44,21 @@ fi gpu_temp=$(echo "$gpu_metrics" | awk -F', ' '{print $1}' | awk -F': ' '{print $2}') gpu_load=$(echo "$gpu_metrics" | awk -F', ' '{print $2}' | awk -F': ' '{print $2}') -gpu_color="#FFFFFF" +gpu_color="#FFFFFF" # Colors for GPU Load -if [[ "$gpu_load" != "N/A" ]]; then - if (( $(echo "$gpu_load < 50.0" | bc -l) )); then - gpu_color="#50FA7B" # Green - elif (( $(echo "$gpu_load < 75.0" | bc -l) )); then - gpu_color="#F1FA8C" # Yellow - else - gpu_color="#FF5555" # Red - fi -else - gpu_color="#FFFFFF" # Default color +if [[ $gpu_load != "N/A" ]]; then + if (($(echo "$gpu_load < 50.0" | bc -l))); then + gpu_color="#50FA7B" # Green + elif (($(echo "$gpu_load < 75.0" | bc -l))); then + gpu_color="#F1FA8C" # Yellow + else + gpu_color="#FF5555" # Red + fi +else + gpu_color="#FFFFFF" # Default color fi # Output< echo -e " ${gpu_temp}, ${gpu_load}%" echo -echo "#FFFFFF" # Default color for fallback (ignored if markup is enabled) - +echo "#FFFFFF" # Default color for fallback (ignored if markup is enabled) diff --git a/i3-configuration/i3blocks/motherboard_temp.sh b/i3-configuration/i3blocks/motherboard_temp.sh index ca0531f..77e47c5 100755 --- a/i3-configuration/i3blocks/motherboard_temp.sh +++ b/i3-configuration/i3blocks/motherboard_temp.sh @@ -4,24 +4,23 @@ temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C') # Ensure the temperature is a valid number -if [[ ! "$temp" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then - echo " MB: N/A" - echo - echo "#FF5555" # Red color for error - exit 1 +if [[ ! $temp =~ ^[0-9]+(\.[0-9]+)?$ ]]; then + echo " MB: N/A" + echo + echo "#FF5555" # Red color for error + exit 1 fi # Define temperature thresholds -if (( $(echo "$temp < 50.0" | bc -l) )); then - color="#50FA7B" # Green for OK temperature -elif (( $(echo "$temp < 70.0" | bc -l) )); then - color="#F1FA8C" # Yellow for warning temperature +if (($(echo "$temp < 50.0" | bc -l))); then + color="#50FA7B" # Green for OK temperature +elif (($(echo "$temp < 70.0" | bc -l))); then + color="#F1FA8C" # Yellow for warning temperature else - color="#FF5555" # Red for high temperature + color="#FF5555" # Red for high temperature fi # Output the temperature with the color -echo " ${temp}°C" #  is a thermometer icon +echo " ${temp}°C" #  is a thermometer icon echo echo $color - diff --git a/i3-configuration/i3blocks/network_monitor.sh b/i3-configuration/i3blocks/network_monitor.sh index 8987549..7c845c0 100755 --- a/i3-configuration/i3blocks/network_monitor.sh +++ b/i3-configuration/i3blocks/network_monitor.sh @@ -2,23 +2,28 @@ # Function to detect all active network interfaces detect_interfaces() { - interfaces=() - for interface in /sys/class/net/*; do - interface=$(basename "$interface") - if [[ "$interface" != "lo" && -d "/sys/class/net/$interface" && "$(cat /sys/class/net/$interface/operstate)" == "up" ]]; then - interfaces+=("$interface") - fi - done - echo "${interfaces[@]}" + local iface_path iface state + for iface_path in /sys/class/net/*; do + iface=$(basename "$iface_path") + if [[ $iface == "lo" || ! -d "/sys/class/net/$iface" ]]; then + continue + fi + if [[ -r "/sys/class/net/$iface/operstate" ]]; then + state=$(< "/sys/class/net/$iface/operstate") + if [[ $state == "up" ]]; then + printf '%s\n' "$iface" + fi + fi + done } # Detect all active network interfaces -interfaces=$(detect_interfaces) +mapfile -t interfaces < <(detect_interfaces) # If no active interfaces are found, exit -if [ -z "$interfaces" ]; then - echo "No active network interfaces found" - exit 1 +if [ "${#interfaces[@]}" -eq 0 ]; then + echo "No active network interfaces found" + exit 1 fi # Initialize total RX and TX bytes @@ -34,46 +39,50 @@ current_time=$(date +%s) last_time=$current_time # Iterate over each interface and accumulate RX and TX bytes -for interface in $interfaces; do - rx_path="/sys/class/net/$interface/statistics/rx_bytes" - tx_path="/sys/class/net/$interface/statistics/tx_bytes" +for interface in "${interfaces[@]}"; do + rx_path="/sys/class/net/$interface/statistics/rx_bytes" + tx_path="/sys/class/net/$interface/statistics/tx_bytes" - rx_now=$(cat $rx_path 2>/dev/null) - tx_now=$(cat $tx_path 2>/dev/null) + if ! read -r rx_now < "$rx_path"; then + rx_now=0 + fi + if ! read -r tx_now < "$tx_path"; then + tx_now=0 + fi - state_file="/tmp/network_monitor_$interface" - if [ -f "$state_file" ]; then - read last_rx last_tx last_time < "$state_file" - else - last_rx=$rx_now - last_tx=$tx_now - fi + state_file="/tmp/network_monitor_$interface" + if [ -f "$state_file" ]; then + read -r last_rx last_tx last_time < "$state_file" + else + last_rx=$rx_now + last_tx=$tx_now + fi - total_rx_now=$((total_rx_now + rx_now)) - total_tx_now=$((total_tx_now + tx_now)) - total_last_rx=$((total_last_rx + last_rx)) - total_last_tx=$((total_last_tx + last_tx)) + total_rx_now=$((total_rx_now + rx_now)) + total_tx_now=$((total_tx_now + tx_now)) + total_last_rx=$((total_last_rx + last_rx)) + total_last_tx=$((total_last_tx + last_tx)) - # Save current RX and TX bytes for the next check - echo "$rx_now $tx_now $current_time" > "$state_file" + # Save current RX and TX bytes for the next check + echo "$rx_now $tx_now $current_time" > "$state_file" done # Calculate time difference time_diff=$((current_time - last_time)) # Calculate total download and upload speeds in bytes per second -if (( time_diff > 0 )); then - total_rx_rate=$(( (total_rx_now - total_last_rx) / time_diff )) - total_tx_rate=$(( (total_tx_now - total_last_tx) / time_diff )) +if ((time_diff > 0)); then + total_rx_rate=$(((total_rx_now - total_last_rx) / time_diff)) + total_tx_rate=$(((total_tx_now - total_last_tx) / time_diff)) else - total_rx_rate=0 - total_tx_rate=0 + total_rx_rate=0 + total_tx_rate=0 fi # Convert speeds to human-readable format -rx_rate_human=$(numfmt --to=iec --suffix=B/s --padding=8 $total_rx_rate) -tx_rate_human=$(numfmt --to=iec --suffix=B/s --padding=8 $total_tx_rate) +rx_rate_human=$(numfmt --to=iec --suffix=B/s --padding=8 "$total_rx_rate") +tx_rate_human=$(numfmt --to=iec --suffix=B/s --padding=8 "$total_tx_rate") # Store the result of printf into a string and echo it printf " %s  %s\n" "$rx_rate_human" "$tx_rate_human" -echo "#50FA7B" \ No newline at end of file +echo "#50FA7B" diff --git a/i3-configuration/i3blocks/pc_startup_status.sh b/i3-configuration/i3blocks/pc_startup_status.sh index 3bbc5b5..058e683 100755 --- a/i3-configuration/i3blocks/pc_startup_status.sh +++ b/i3-configuration/i3blocks/pc_startup_status.sh @@ -4,65 +4,69 @@ # Function to check if today is a monitored day is_monitored_day() { - local day_of_week=$(date +%u) - if [[ "$day_of_week" == "1" ]] || [[ "$day_of_week" == "5" ]] || [[ "$day_of_week" == "6" ]] || [[ "$day_of_week" == "7" ]]; then - return 0 - else - return 1 - fi + local day_of_week + day_of_week=$(date +%u) + if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then + return 0 + else + return 1 + fi } # Function to check if current time is in window is_current_time_in_window() { - local current_hour=$(date +%H) - local current_hour_num=$((10#$current_hour)) - if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then - return 0 - else - return 1 - fi + local current_hour current_hour_num + current_hour=$(date +%H) + current_hour_num=$((10#$current_hour)) + if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then + return 0 + else + return 1 + fi } # Function to check if PC was booted in window today was_booted_in_window_today() { - local today=$(date +%Y-%m-%d) - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - local boot_date=$(echo "$boot_time" | cut -d' ' -f1) - - if [[ "$boot_date" != "$today" ]]; then - return 1 - fi - - local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) - local boot_hour_num=$((10#$boot_hour)) - - if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then - return 0 - else - return 1 - fi + local today uptime_seconds boot_time boot_date + today=$(date +%Y-%m-%d) + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") + boot_date=$(echo "$boot_time" | cut -d' ' -f1) + + if [[ $boot_date != "$today" ]]; then + return 1 + fi + + local boot_hour boot_hour_num + boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) + boot_hour_num=$((10#$boot_hour)) + + if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then + return 0 + else + return 1 + fi } # Main logic if ! is_monitored_day; then - # Not a monitored day - echo "PC:skip" - echo - echo "#888888" # Gray + # Not a monitored day + echo "PC:skip" + echo + echo "#888888" # Gray elif is_current_time_in_window; then - # Currently in the window - all good - echo "PC:live" - echo - echo "#00FF00" # Green + # Currently in the window - all good + echo "PC:live" + echo + echo "#00FF00" # Green elif was_booted_in_window_today; then - # Was booted in window today - compliant - echo "PC:ok" - echo - echo "#00FF00" # Green + # Was booted in window today - compliant + echo "PC:ok" + echo + echo "#00FF00" # Green else - # Was NOT booted in window today - non-compliant - echo "PC:warn" - echo - echo "#FF0000" # Red + # Was NOT booted in window today - non-compliant + echo "PC:warn" + echo + echo "#FF0000" # Red fi diff --git a/i3-configuration/i3blocks/volume.sh b/i3-configuration/i3blocks/volume.sh index eae11bb..c6e1112 100755 --- a/i3-configuration/i3blocks/volume.sh +++ b/i3-configuration/i3blocks/volume.sh @@ -1,20 +1,19 @@ - #!/bin/bash # Get the current volume level and mute status volume=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | tr -d '%') mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}') +color="#50FA7B" # Determine icon and color based on mute status if [ "$mute" = "yes" ]; then - icon="🔇" # Muted + icon="🔇" # Muted + color="#FF5555" else - icon="🔊" # Volume icon - color="#50FA7B" # Green + icon="🔊" # Volume icon fi # Output the volume with icon and color echo "$icon $volume%" echo -echo $color - +echo "$color" diff --git a/i3-configuration/i3blocks/warp_status.sh b/i3-configuration/i3blocks/warp_status.sh index fba0fb8..a4ffa9d 100755 --- a/i3-configuration/i3blocks/warp_status.sh +++ b/i3-configuration/i3blocks/warp_status.sh @@ -2,25 +2,25 @@ # Check if warp-cli is installed if ! command -v warp-cli &> /dev/null; then - echo " N/A" - exit 0 + echo " N/A" + exit 0 fi # Get the status from warp-cli -status=$(warp-cli status 2>/dev/null | grep "Status update:" | awk '{print $3}') +status=$(warp-cli status 2> /dev/null | grep "Status update:" | awk '{print $3}') # Display the status with an icon if [ "$status" = "Connected" ]; then - echo "🔒 !!! WARP CONNECTED !!!" - echo - echo "#FFFF00" # Yellow + echo "🔒 !!! WARP CONNECTED !!!" + echo + echo "#FFFF00" # Yellow elif [ "$status" = "Disconnected" ]; then - echo "WARP disconnected" - echo - echo "#00FF00" # Green + echo "WARP disconnected" + echo + echo "#00FF00" # Green else - echo "⚠️ ! WARP unknown !" - echo - echo "#FF0000" # Red - exit 0 + echo "⚠️ ! WARP unknown !" + echo + echo "#FF0000" # Red + exit 0 fi diff --git a/i3-configuration/i3blocks/wifi_monitor.sh b/i3-configuration/i3blocks/wifi_monitor.sh index a85bb88..e818c62 100755 --- a/i3-configuration/i3blocks/wifi_monitor.sh +++ b/i3-configuration/i3blocks/wifi_monitor.sh @@ -5,23 +5,23 @@ wifi_interface=$(iw dev | awk '$1=="Interface"{print $2}') # If no WiFi interface is found, exit if [ -z "$wifi_interface" ]; then - echo " down" - exit 1 + echo " down" + exit 1 fi # Get the WiFi details -wifi_info=$(iwconfig $wifi_interface 2>/dev/null) +wifi_info=$(iwconfig "$wifi_interface" 2> /dev/null) # Extract the SSID and signal strength ssid=$(echo "$wifi_info" | awk -F '"' '/ESSID/ {print $2}') -signal=$(echo "$wifi_info" | awk '/Signal level/ {print $4}' | tr -d 'level=') +signal=$(echo "$wifi_info" | awk '/Signal level/ {print $4}' | sed 's/level=//') # Get the IP address -ip_address=$(ip addr show $wifi_interface | awk '/inet / {print $2}' | cut -d/ -f1) +ip_address=$(ip addr show "$wifi_interface" | awk '/inet / {print $2}' | cut -d/ -f1) # Output the result if [ -z "$ssid" ]; then - echo " down" + echo " down" else - echo " $ssid ($signal dBm) $ip_address" -fi \ No newline at end of file + echo " $ssid ($signal dBm) $ip_address" +fi diff --git a/i3-configuration/install.sh b/i3-configuration/install.sh index f56538c..939175b 100755 --- a/i3-configuration/install.sh +++ b/i3-configuration/install.sh @@ -2,40 +2,40 @@ # Function to detect if the system is Ubuntu is_ubuntu() { - [ -f /etc/os-release ] && grep -qi 'ubuntu' /etc/os-release + [ -f /etc/os-release ] && grep -qi 'ubuntu' /etc/os-release } # Function to detect screen resolution and set font size set_font_size() { - resolution=$(xdpyinfo | grep dimensions | awk '{print $2}') - width=$(echo $resolution | cut -d 'x' -f 1) - # Do not change this font size, it actually makes i3blocks unbearable to look at: - # Icons (like for slack) are too small and i3blocks are too big - # Network monitor jumping becomes annoying - if [ "$width" -gt 1920 ]; then - echo "8" - else - echo "8" - fi + resolution=$(xdpyinfo | grep dimensions | awk '{print $2}') + width=$(echo "$resolution" | cut -d 'x' -f 1) + # Do not change this font size, it actually makes i3blocks unbearable to look at: + # Icons (like for slack) are too small and i3blocks are too big + # Network monitor jumping becomes annoying + if [ "$width" -gt 1920 ]; then + echo "8" + else + echo "8" + fi } # Check if Intel GPU is detected if lspci | grep -i 'vga' | grep -i 'intel'; then - if is_ubuntu; then - sudo apt-get update - sudo apt-get install -y intel-gpu-tools - sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top - else - yes | sudo pacman -S --needed intel-gpu-tools - sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top - fi + if is_ubuntu; then + sudo apt-get update + sudo apt-get install -y intel-gpu-tools + sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top + else + yes | sudo pacman -S --needed intel-gpu-tools + sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top + fi fi if is_ubuntu; then - sudo apt-get update - sudo apt-get install -y fonts-dejavu-core fonts-noto fonts-font-awesome bc jq iw pulseaudio-utils + sudo apt-get update + sudo apt-get install -y fonts-dejavu-core fonts-noto fonts-font-awesome bc jq iw pulseaudio-utils else - yes | sudo pacman -S --needed ttf-dejavu noto-fonts ttf-font-awesome bc jq iw acpi + yes | sudo pacman -S --needed ttf-dejavu noto-fonts ttf-font-awesome bc jq iw acpi fi # Set font size based on screen resolution diff --git a/scripts/digital_wellbeing/install_leechblock.sh b/scripts/digital_wellbeing/install_leechblock.sh index d505697..554cad9 100755 --- a/scripts/digital_wellbeing/install_leechblock.sh +++ b/scripts/digital_wellbeing/install_leechblock.sh @@ -12,17 +12,17 @@ SCRIPT_NAME=${0##*/} info() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } -err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } +err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } require_cmd() { - if ! command -v "$1" >/dev/null 2>&1; then + if ! command -v "$1" > /dev/null 2>&1; then err "Missing dependency: $1" MISSING=1 fi } usage() { - cat </dev/null 2>&1; then +if ! command -v jq > /dev/null 2>&1; then warn "jq not found — will fall back to a simpler tag detection method." fi -[[ $MISSING -eq 1 ]] && { err "Please install missing tools and re-run."; exit 1; } +[[ $MISSING -eq 1 ]] && { + err "Please install missing tools and re-run." + exit 1 +} REPO_OWNER="proginosko" REPO_NAME="LeechBlockNG" get_latest_tag() { local tag - if command -v jq >/dev/null 2>&1; then + if command -v jq > /dev/null 2>&1; then tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | jq -r '.tag_name // empty' || true) - if [[ -n "$tag" && "$tag" != "null" ]]; then - echo "$tag"; return 0 + if [[ -n $tag && $tag != "null" ]]; then + echo "$tag" + return 0 fi fi # Fallback: follow redirect for /releases/latest to extract tag tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true) - if [[ -n "$tag" ]]; then - echo "$tag"; return 0 + if [[ -n $tag ]]; then + echo "$tag" + return 0 fi return 1 } -if [[ -z "$VERSION" ]]; then +if [[ -z $VERSION ]]; then info "Resolving latest release tag from GitHub…" if ! VERSION=$(get_latest_tag); then - err "Failed to determine latest version tag"; exit 1 + err "Failed to determine latest version tag" + exit 1 fi fi -if [[ ! "$VERSION" =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then +if [[ ! $VERSION =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway." fi -VERSION=${VERSION#v} # strip leading v for folder names +VERSION=${VERSION#v} # strip leading v for folder names TAG="v${VERSION}" XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} @@ -108,7 +125,7 @@ INSTALL_ROOT="$XDG_DATA_HOME/leechblockng" VERSION_DIR="$INSTALL_ROOT/$VERSION" CURRENT_LINK="$INSTALL_ROOT/current" -if [[ -d "$VERSION_DIR" && $FORCE -ne 1 ]]; then +if [[ -d $VERSION_DIR && $FORCE -ne 1 ]]; then info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)." else info "Downloading LeechBlockNG $TAG source from GitHub…" @@ -122,11 +139,14 @@ else tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract" # The archive usually extracts to REPO_NAME-TAG/ … src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true) - [[ -z "$src_root" ]] && { err "Could not locate extracted source root"; exit 1; } + [[ -z $src_root ]] && { + err "Could not locate extracted source root" + exit 1 + } # Find the extension manifest (support a couple of common layouts) manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true) - if [[ -z "$manifest_path" ]]; then + if [[ -z $manifest_path ]]; then err "manifest.json not found in the extracted archive. The project layout may have changed." exit 1 fi @@ -137,30 +157,30 @@ else info "Installing to $VERSION_DIR…" mkdir -p "$VERSION_DIR" # Copy the extension directory as-is (avoid bringing tests or build scripts) - rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2>/dev/null || cp -a "$ext_dir/." "$VERSION_DIR/" + rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2> /dev/null || cp -a "$ext_dir/." "$VERSION_DIR/" ln -sfn "$VERSION_DIR" "$CURRENT_LINK" fi -EXT_PATH="$CURRENT_LINK" # stable path used by wrappers +EXT_PATH="$CURRENT_LINK" # stable path used by wrappers # Detect browsers declare -A BROWSERS BROWSERS=( [chromium]="Chromium" - [google-chrome-stable]="Google Chrome" - [google-chrome]="Google Chrome" - [brave-browser]="Brave" - [vivaldi-stable]="Vivaldi" + [google - chrome - stable]="Google Chrome" + [google - chrome]="Google Chrome" + [brave - browser]="Brave" + [vivaldi - stable]="Vivaldi" [vivaldi]="Vivaldi" [opera]="Opera" - [thorium-browser]="Thorium" + [thorium - browser]="Thorium" ) declare -A FIREFOXES FIREFOXES=( [firefox]="Firefox" - [firefox-developer-edition]="Firefox Developer Edition" + [firefox - developer - edition]="Firefox Developer Edition" [librewolf]="LibreWolf" ) @@ -173,15 +193,17 @@ user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications" mkdir -p "$user_apps_dir" create_wrapper_and_desktop() { - local bin="$1"; shift - local pretty="$1"; shift + local bin="$1" + shift + local pretty="$1" + shift local wrapper="$wrap_bin_dir/${bin}-with-leechblock" local real_bin real_bin=$(command -v "$bin" || true) - [[ -z "$real_bin" ]] && return + [[ -z $real_bin ]] && return - cat >"$wrapper" < "$wrapper" << WRAP #!/usr/bin/env bash exec "$real_bin" --load-extension="$EXT_PATH" "$@" WRAP @@ -189,18 +211,18 @@ WRAP # Try to reuse icon from an existing desktop file if available local sys_desktop existing_icon existing_name categories - sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2>/dev/null | head -n1 || true) - if [[ -n "$sys_desktop" ]]; then + sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2> /dev/null | head -n1 || true) + if [[ -n $sys_desktop ]]; then existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true) existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true) categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true) fi - [[ -z "$existing_icon" ]] && existing_icon="$bin" - [[ -z "$existing_name" ]] && existing_name="$pretty" - [[ -z "$categories" ]] && categories="Network;WebBrowser;" + [[ -z $existing_icon ]] && existing_icon="$bin" + [[ -z $existing_name ]] && existing_name="$pretty" + [[ -z $categories ]] && categories="Network;WebBrowser;" local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop" - cat >"$desktop_file" < "$desktop_file" << DESK [Desktop Entry] Name=${existing_name} (LeechBlock) Exec=${wrapper} %U @@ -218,14 +240,14 @@ DESK info "Detecting installed browsers…" for bin in "${!BROWSERS[@]}"; do - if command -v "$bin" >/dev/null 2>&1; then + if command -v "$bin" > /dev/null 2>&1; then create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}" fi done ff_found=0 for bin in "${!FIREFOXES[@]}"; do - if command -v "$bin" >/dev/null 2>&1; then + if command -v "$bin" > /dev/null 2>&1; then ff_found=1 fi done @@ -239,7 +261,7 @@ fi if [[ $ff_found -eq 1 ]]; then echo warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing." - cat </dev/null 2>&1; then + if command -v firefox > /dev/null 2>&1; then POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution") fi - if command -v firefox-developer-edition >/dev/null 2>&1; then + if command -v firefox-developer-edition > /dev/null 2>&1; then POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution") fi - if command -v librewolf >/dev/null 2>&1; then + if command -v librewolf > /dev/null 2>&1; then POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution") fi # Generic mozilla path as fallback @@ -291,7 +313,7 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then if sudo test -f "$existing"; then info "Merging into existing policies.json at $existing" sudo cp "$existing" "$tmp_pol" - if command -v jq >/dev/null 2>&1; then + if command -v jq > /dev/null 2>&1; then merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" ' .policies |= (. // {}) | .policies.ExtensionSettings |= (. // {}) | @@ -300,16 +322,17 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then .policies.ExtensionSettings[$id].installation_mode = "force_installed" | .policies.ExtensionSettings[$id].install_url = $url ' "$tmp_pol") || merged="" - if [[ -n "$merged" ]]; then + if [[ -n $merged ]]; then printf '%s\n' "$merged" > "$tmp_pol" else warn "jq merge failed; skipping $pol_target" - rm -f "$tmp_pol"; continue + rm -f "$tmp_pol" + continue fi else warn "jq not available; creating minimal policies.json (existing file will be backed up)." sudo cp "$existing" "${existing}.bak.$(date +%s)" - cat > "$tmp_pol" < "$tmp_pol" << JSON { "policies": { "ExtensionSettings": { @@ -325,7 +348,7 @@ JSON fi else info "Creating new policies.json at $pol_target" - cat > "$tmp_pol" < "$tmp_pol" << JSON { "policies": { "ExtensionSettings": { diff --git a/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh b/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh index 1e07980..3eefad5 100755 --- a/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh +++ b/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh @@ -14,7 +14,6 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' -BOLD='\033[1m' NC='\033[0m' # No Color # Script locations @@ -57,7 +56,7 @@ else echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}" fi chmod +x "$WRAPPER_DEST" -chmod 644 "$WORDS_DEST" "$BLOCKED_DEST" "$WHITELIST_DEST" 2>/dev/null || true +chmod 644 "$WORDS_DEST" "$BLOCKED_DEST" "$WHITELIST_DEST" 2> /dev/null || true # Automatically use symbolic link installation method echo -e "${YELLOW}Installing using symbolic link method...${NC}" @@ -75,4 +74,4 @@ sed -i 's|PACMAN_BIN="\/usr\/bin\/pacman"|PACMAN_BIN="\/usr\/bin\/pacman.orig"|g echo -e "${BLUE}Creating symbolic link...${NC}" ln -sf "$WRAPPER_DEST" /usr/bin/pacman echo -e "${GREEN}Installation complete!${NC}" -echo -e "Pacman is now wrapped. The original pacman is available at ${CYAN}/usr/bin/pacman.orig${NC}" \ No newline at end of file +echo -e "Pacman is now wrapped. The original pacman is available at ${CYAN}/usr/bin/pacman.orig${NC}" diff --git a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh index 70c4046..a1f339b 100755 --- a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh +++ b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh @@ -18,647 +18,648 @@ declare -a WHITELISTED_NAMES_LIST=() POLICY_LISTS_LOADED=0 load_policy_lists() { - if [[ $POLICY_LISTS_LOADED -eq 1 ]]; then - return - fi + if [[ $POLICY_LISTS_LOADED -eq 1 ]]; then + return + fi - local script_dir - script_dir="$(dirname "$(readlink -f "$0")")" - local blocked_file="$script_dir/pacman_blocked_keywords.txt" - local whitelist_file="$script_dir/pacman_whitelist.txt" + local script_dir + script_dir="$(dirname "$(readlink -f "$0")")" + local blocked_file="$script_dir/pacman_blocked_keywords.txt" + local whitelist_file="$script_dir/pacman_whitelist.txt" - if [[ -f "$blocked_file" ]]; then - mapfile -t BLOCKED_KEYWORDS_LIST < <(sed 's/\r$//' "$blocked_file" | grep -Ev '^[[:space:]]*(#|$)' || true) - else - BLOCKED_KEYWORDS_LIST=() - echo -e "${YELLOW}Warning:${NC} Missing blocked keywords file at $blocked_file" >&2 - fi + if [[ -f $blocked_file ]]; then + mapfile -t BLOCKED_KEYWORDS_LIST < <(sed 's/\r$//' "$blocked_file" | grep -Ev '^[[:space:]]*(#|$)' || true) + else + BLOCKED_KEYWORDS_LIST=() + echo -e "${YELLOW}Warning:${NC} Missing blocked keywords file at $blocked_file" >&2 + fi - if [[ -f "$whitelist_file" ]]; then - mapfile -t WHITELISTED_NAMES_LIST < <(sed 's/\r$//' "$whitelist_file" | grep -Ev '^[[:space:]]*(#|$)' || true) - else - WHITELISTED_NAMES_LIST=() - fi + if [[ -f $whitelist_file ]]; then + mapfile -t WHITELISTED_NAMES_LIST < <(sed 's/\r$//' "$whitelist_file" | grep -Ev '^[[:space:]]*(#|$)' || true) + else + WHITELISTED_NAMES_LIST=() + fi - for i in "${!BLOCKED_KEYWORDS_LIST[@]}"; do - BLOCKED_KEYWORDS_LIST[$i]="${BLOCKED_KEYWORDS_LIST[$i],,}" - done + for i in "${!BLOCKED_KEYWORDS_LIST[@]}"; do + BLOCKED_KEYWORDS_LIST[i]="${BLOCKED_KEYWORDS_LIST[i],,}" + done - for i in "${!WHITELISTED_NAMES_LIST[@]}"; do - WHITELISTED_NAMES_LIST[$i]="${WHITELISTED_NAMES_LIST[$i],,}" - done + for i in "${!WHITELISTED_NAMES_LIST[@]}"; do + WHITELISTED_NAMES_LIST[i]="${WHITELISTED_NAMES_LIST[i],,}" + done - POLICY_LISTS_LOADED=1 + POLICY_LISTS_LOADED=1 } # Determine if this invocation may perform a transaction (upgrade/install/remove) needs_unlock() { - # If args include -S (install/upgrade), -U (local install), or -R (remove), we unlock - # Also include -Su/-Syu/-Syuu when -S is part of the combined flag - for arg in "$@"; do - case "$arg" in - -S*|-U|-R|--sync|--upgrade|--remove) - return 0 ;; - esac - done - return 1 + # If args include -S (install/upgrade), -U (local install), or -R (remove), we unlock + # Also include -Su/-Syu/-Syuu when -S is part of the combined flag + for arg in "$@"; do + case "$arg" in + -S* | -U | -R | --sync | --upgrade | --remove) + return 0 + ;; + esac + done + return 1 } # Run pre/post hooks for /etc/hosts guard if present pre_unlock_hosts() { - local pre="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh" - if [[ -x "$pre" ]]; then - echo -e "${CYAN}[hosts-guard] Preparing /etc/hosts for transaction...${NC}" >&2 - /bin/bash "$pre" || true - fi + local pre="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh" + if [[ -x $pre ]]; then + echo -e "${CYAN}[hosts-guard] Preparing /etc/hosts for transaction...${NC}" >&2 + /bin/bash "$pre" || true + fi } post_relock_hosts() { - local post="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh" - if [[ -x "$post" ]]; then - /bin/bash "$post" || true - echo -e "${CYAN}[hosts-guard] Protections re-applied to /etc/hosts.${NC}" >&2 - fi + local post="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh" + if [[ -x $post ]]; then + /bin/bash "$post" || true + echo -e "${CYAN}[hosts-guard] Protections re-applied to /etc/hosts.${NC}" >&2 + fi } - # Ensure periodic system services (timer/monitor) are set up; if not, trigger setup ensure_periodic_maintenance() { - # Only proceed if systemd/systemctl is available - if ! command -v systemctl >/dev/null 2>&1; then - return 0 - fi + # Only proceed if systemd/systemctl is available + if ! command -v systemctl > /dev/null 2>&1; then + return 0 + fi - local timer_unit="periodic-system-maintenance.timer" - local startup_unit="periodic-system-startup.service" - local monitor_unit="hosts-file-monitor.service" - local needs_setup=0 + local timer_unit="periodic-system-maintenance.timer" + local startup_unit="periodic-system-startup.service" + local monitor_unit="hosts-file-monitor.service" + local needs_setup=0 - # Timer should be enabled and active - systemctl --quiet is-enabled "$timer_unit" || needs_setup=1 - systemctl --quiet is-active "$timer_unit" || needs_setup=1 + # Timer should be enabled and active + systemctl --quiet is-enabled "$timer_unit" || needs_setup=1 + systemctl --quiet is-active "$timer_unit" || needs_setup=1 - # Monitor should be enabled and active - systemctl --quiet is-enabled "$monitor_unit" || needs_setup=1 - systemctl --quiet is-active "$monitor_unit" || needs_setup=1 + # Monitor should be enabled and active + systemctl --quiet is-enabled "$monitor_unit" || needs_setup=1 + systemctl --quiet is-active "$monitor_unit" || needs_setup=1 - # Startup service should be enabled (it’s oneshot and may not be active except at boot) - systemctl --quiet is-enabled "$startup_unit" || needs_setup=1 + # Startup service should be enabled (it’s oneshot and may not be active except at boot) + systemctl --quiet is-enabled "$startup_unit" || needs_setup=1 - if [[ $needs_setup -eq 0 ]]; then - return 0 - fi + if [[ $needs_setup -eq 0 ]]; then + return 0 + fi - echo -e "${YELLOW}Periodic maintenance services missing or inactive. Running setup...${NC}" >&2 + echo -e "${YELLOW}Periodic maintenance services missing or inactive. Running setup...${NC}" >&2 - # Try to locate setup_periodic_system.sh - local setup_script="" - local self_dir - self_dir="$(dirname "$(readlink -f "$0")")" - if [[ -f "$self_dir/setup_periodic_system.sh" ]]; then - setup_script="$self_dir/setup_periodic_system.sh" - elif [[ -f "$HOME/linux-configuration/scripts/setup_periodic_system.sh" ]]; then - setup_script="$HOME/linux-configuration/scripts/setup_periodic_system.sh" - fi + # Try to locate setup_periodic_system.sh + local setup_script="" + local self_dir + self_dir="$(dirname "$(readlink -f "$0")")" + if [[ -f "$self_dir/setup_periodic_system.sh" ]]; then + setup_script="$self_dir/setup_periodic_system.sh" + elif [[ -f "$HOME/linux-configuration/scripts/setup_periodic_system.sh" ]]; then + setup_script="$HOME/linux-configuration/scripts/setup_periodic_system.sh" + fi - if [[ -n "$setup_script" ]]; then - if [[ $EUID -ne 0 ]]; then - sudo bash "$setup_script" - else - bash "$setup_script" - fi - echo -e "${CYAN}Tip:${NC} To disable these later:" >&2 - echo " sudo systemctl disable periodic-system-maintenance.timer" >&2 - echo " sudo systemctl disable periodic-system-startup.service" >&2 - echo " sudo systemctl disable hosts-file-monitor.service" >&2 + if [[ -n $setup_script ]]; then + if [[ $EUID -ne 0 ]]; then + sudo bash "$setup_script" else - echo -e "${RED}Could not locate setup_periodic_system.sh to configure services automatically.${NC}" >&2 + bash "$setup_script" fi + echo -e "${CYAN}Tip:${NC} To disable these later:" >&2 + echo " sudo systemctl disable periodic-system-maintenance.timer" >&2 + echo " sudo systemctl disable periodic-system-startup.service" >&2 + echo " sudo systemctl disable hosts-file-monitor.service" >&2 + else + echo -e "${RED}Could not locate setup_periodic_system.sh to configure services automatically.${NC}" >&2 + fi } # Function to display help function show_help() { - echo -e "${BOLD}Pacman Wrapper Help${NC}" - echo "This wrapper adds helpful features while preserving all pacman functionality." - echo "" - echo "Additional commands:" - echo " --help-wrapper Show this help message" + echo -e "${BOLD}Pacman Wrapper Help${NC}" + echo "This wrapper adds helpful features while preserving all pacman functionality." + echo "" + echo "Additional commands:" + echo " --help-wrapper Show this help message" } # Function to display a message before executing function display_operation() { - case "$1" in - -S|-Sy|-S\ *) - echo -e "${BLUE}Installing packages...${NC}" >&2 - ;; - -Syu|-Syyu) - echo -e "${BLUE}Updating system...${NC}" >&2 - ;; - -R|-Rs|-Rns|-R\ *) - echo -e "${YELLOW}Removing packages...${NC}" >&2 - ;; - -Ss|-Ss\ *) - echo -e "${CYAN}Searching for packages...${NC}" >&2 - ;; - -Q|-Qs|-Qi|-Ql|-Q\ *) - echo -e "${CYAN}Querying package database...${NC}" >&2 - ;; - -U|-U\ *) - echo -e "${BLUE}Installing local packages...${NC}" >&2 - ;; - -Scc) - echo -e "${YELLOW}Cleaning package cache...${NC}" >&2 - ;; - *) - echo -e "${CYAN}Executing pacman command...${NC}" >&2 - ;; - esac + case "$1" in + -S | -Sy | -S\ *) + echo -e "${BLUE}Installing packages...${NC}" >&2 + ;; + -Syu | -Syyu) + echo -e "${BLUE}Updating system...${NC}" >&2 + ;; + -R | -Rs | -Rns | -R\ *) + echo -e "${YELLOW}Removing packages...${NC}" >&2 + ;; + -Ss | -Ss\ *) + echo -e "${CYAN}Searching for packages...${NC}" >&2 + ;; + -Q | -Qs | -Qi | -Ql | -Q\ *) + echo -e "${CYAN}Querying package database...${NC}" >&2 + ;; + -U | -U\ *) + echo -e "${BLUE}Installing local packages...${NC}" >&2 + ;; + -Scc) + echo -e "${YELLOW}Cleaning package cache...${NC}" >&2 + ;; + *) + echo -e "${CYAN}Executing pacman command...${NC}" >&2 + ;; + esac } # Helper: return 0 if the given package name is blocked by policy function is_blocked_package_name() { - load_policy_lists - local normalized="${1,,}" + load_policy_lists + local normalized="${1,,}" - for allowed in "${WHITELISTED_NAMES_LIST[@]}"; do - if [[ "$normalized" == "$allowed" ]]; then - return 1 - fi - done + for allowed in "${WHITELISTED_NAMES_LIST[@]}"; do + if [[ $normalized == "$allowed" ]]; then + return 1 + fi + done - for keyword in "${BLOCKED_KEYWORDS_LIST[@]}"; do - if [[ -n "$keyword" && "$normalized" == *"$keyword"* ]]; then - return 0 - fi - done + for keyword in "${BLOCKED_KEYWORDS_LIST[@]}"; do + if [[ -n $keyword && $normalized == *"$keyword"* ]]; then + return 0 + fi + done - return 1 + return 1 } # Helper: detect if current invocation includes --noconfirm function has_noconfirm_flag() { - for arg in "$@"; do - if [[ "$arg" == "--noconfirm" ]]; then - return 0 - fi - done - return 1 + for arg in "$@"; do + if [[ $arg == "--noconfirm" ]]; then + return 0 + fi + done + return 1 } # Handle stale pacman database lock if present and no package managers are running check_and_handle_db_lock() { - local lock_file="/var/lib/pacman/db.lck" - # Quick exit if no lock - if [[ ! -e "$lock_file" ]]; then - return 0 + local lock_file="/var/lib/pacman/db.lck" + # Quick exit if no lock + if [[ ! -e $lock_file ]]; then + return 0 + fi + + # Determine which processes actually have the lock open + local -a holders=() + if command -v fuser > /dev/null 2>&1; then + mapfile -t holders < <(fuser "$lock_file" 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) + elif command -v lsof > /dev/null 2>&1; then + mapfile -t holders < <(lsof -t "$lock_file" 2> /dev/null | grep -E '^[0-9]+$' || true) + else + holders=() + fi + + # Filter out our own PID if it somehow appears + if [[ ${#holders[@]} -gt 0 ]]; then + local -a filtered=() + for pid in "${holders[@]}"; do + [[ $pid -eq $$ ]] && continue + filtered+=("$pid") + done + holders=("${filtered[@]}") + fi + + if [[ ${#holders[@]} -gt 0 ]]; then + local pac_holder=0 + local gui_holder=0 + for pid in "${holders[@]}"; do + local comm args lower + comm=$(ps -p "$pid" -o comm= 2> /dev/null || true) + args=$(ps -p "$pid" -o args= 2> /dev/null || true) + lower="${comm,,} ${args,,}" + if [[ $lower == *" pacman"* || $lower == pacman* || $lower == *"/pacman "* || $lower == *" pamac"* ]]; then + pac_holder=1 + elif [[ $lower == *packagekit* || $lower == *gnome-software* || $lower == *discover* ]]; then + gui_holder=1 + fi + done + + if [[ $pac_holder -eq 1 ]]; then + echo -e "${RED}Another pacman/pamac transaction is holding the database lock. Try again later.${NC}" >&2 + return 1 fi - # Determine which processes actually have the lock open - local -a holders=() - if command -v fuser >/dev/null 2>&1; then - mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) - elif command -v lsof >/dev/null 2>&1; then - mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true) + if [[ $gui_holder -eq 1 ]]; then + echo -e "${YELLOW}A background software updater is holding the pacman lock. Attempting to stop it...${NC}" >&2 + if command -v systemctl > /dev/null 2>&1; then + systemctl --quiet stop packagekit.service 2> /dev/null || true + systemctl --quiet stop packagekit 2> /dev/null || true + fi + pkill -x packagekitd 2> /dev/null || true + pkill -f gnome-software 2> /dev/null || true + pkill -f discover 2> /dev/null || true + sleep 1 + + # Re-check holders + holders=() + if command -v fuser > /dev/null 2>&1; then + mapfile -t holders < <(fuser "$lock_file" 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) + elif command -v lsof > /dev/null 2>&1; then + mapfile -t holders < <(lsof -t "$lock_file" 2> /dev/null | grep -E '^[0-9]+$' || true) + fi + if [[ ${#holders[@]} -gt 0 ]]; then + echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2 + return 1 + fi + fi + fi + + # Decide whether to remove the lock + local now epoch age + if epoch=$(stat -c %Y "$lock_file" 2> /dev/null); then + now=$(date +%s) + age=$((now - epoch)) + else + age=999999 + fi + + # Auto-remove in non-interactive mode (--noconfirm) or if the lock is older than 10 minutes + if has_noconfirm_flag "$@" || [[ $age -ge 600 ]]; then + echo -e "${YELLOW}Stale pacman lock detected (age: ${age}s). Removing it automatically...${NC}" >&2 + if [[ $EUID -ne 0 ]]; then + sudo rm -f "$lock_file" || return 1 else - holders=() + rm -f "$lock_file" || return 1 fi + return 0 + fi - # Filter out our own PID if it somehow appears - if [[ ${#holders[@]} -gt 0 ]]; then - local -a filtered=() - for pid in "${holders[@]}"; do - [[ "$pid" -eq "$$" ]] && continue - filtered+=("$pid") - done - holders=("${filtered[@]}") - fi - - if [[ ${#holders[@]} -gt 0 ]]; then - local pac_holder=0 - local gui_holder=0 - for pid in "${holders[@]}"; do - local comm args lower - comm=$(ps -p "$pid" -o comm= 2>/dev/null || true) - args=$(ps -p "$pid" -o args= 2>/dev/null || true) - lower="${comm,,} ${args,,}" - if [[ "$lower" == *" pacman"* || "$lower" == pacman* || "$lower" == *"/pacman "* || "$lower" == *" pamac"* ]]; then - pac_holder=1 - elif [[ "$lower" == *packagekit* || "$lower" == *gnome-software* || "$lower" == *discover* ]]; then - gui_holder=1 - fi - done - - if [[ $pac_holder -eq 1 ]]; then - echo -e "${RED}Another pacman/pamac transaction is holding the database lock. Try again later.${NC}" >&2 - return 1 - fi - - if [[ $gui_holder -eq 1 ]]; then - echo -e "${YELLOW}A background software updater is holding the pacman lock. Attempting to stop it...${NC}" >&2 - if command -v systemctl >/dev/null 2>&1; then - systemctl --quiet stop packagekit.service 2>/dev/null || true - systemctl --quiet stop packagekit 2>/dev/null || true - fi - pkill -x packagekitd 2>/dev/null || true - pkill -f gnome-software 2>/dev/null || true - pkill -f discover 2>/dev/null || true - sleep 1 - - # Re-check holders - holders=() - if command -v fuser >/dev/null 2>&1; then - mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) - elif command -v lsof >/dev/null 2>&1; then - mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true) - fi - if [[ ${#holders[@]} -gt 0 ]]; then - echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2 - return 1 - fi - fi - fi - - # Decide whether to remove the lock - local now epoch age - if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then - now=$(date +%s) - age=$(( now - epoch )) + # Interactive prompt (15s timeout) + echo -e "${YELLOW}A pacman lock exists but no active pacman is running.${NC}" >&2 + echo -e "${CYAN}Lock path:${NC} $lock_file (age: ${age}s)" >&2 + read -r -t 15 -p $'Remove stale lock and continue? [y/N]: ' reply || reply="n" + if [[ ${reply,,} == "y" || ${reply,,} == "yes" ]]; then + if [[ $EUID -ne 0 ]]; then + sudo rm -f "$lock_file" || return 1 else - age=999999 + rm -f "$lock_file" || return 1 fi - - # Auto-remove in non-interactive mode (--noconfirm) or if the lock is older than 10 minutes - if has_noconfirm_flag "$@" || [[ $age -ge 600 ]]; then - echo -e "${YELLOW}Stale pacman lock detected (age: ${age}s). Removing it automatically...${NC}" >&2 - if [[ $EUID -ne 0 ]]; then - sudo rm -f "$lock_file" || return 1 - else - rm -f "$lock_file" || return 1 - fi - return 0 - fi - - # Interactive prompt (15s timeout) - echo -e "${YELLOW}A pacman lock exists but no active pacman is running.${NC}" >&2 - echo -e "${CYAN}Lock path:${NC} $lock_file (age: ${age}s)" >&2 - read -r -t 15 -p $'Remove stale lock and continue? [y/N]: ' reply || reply="n" - if [[ "${reply,,}" == "y" || "${reply,,}" == "yes" ]]; then - if [[ $EUID -ne 0 ]]; then - sudo rm -f "$lock_file" || return 1 - else - rm -f "$lock_file" || return 1 - fi - return 0 - fi - echo -e "${RED}Aborting due to existing pacman lock. Close other updaters and retry, or run with --noconfirm to auto-clear stale locks.${NC}" >&2 - return 1 + return 0 + fi + echo -e "${RED}Aborting due to existing pacman lock. Close other updaters and retry, or run with --noconfirm to auto-clear stale locks.${NC}" >&2 + return 1 } # Cleanup: remove any installed blocked packages (in addition to the queued operation) function remove_installed_blocked_packages() { - # args not used; kept for future policy extension - # List installed package names - mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null) - local to_remove=() - for name in "${installed_names[@]}"; do - if is_blocked_package_name "$name"; then - to_remove+=("$name") - fi - done - - if [[ ${#to_remove[@]} -eq 0 ]]; then - return 0 + # args not used; kept for future policy extension + # List installed package names + mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2> /dev/null) + local to_remove=() + for name in "${installed_names[@]}"; do + if is_blocked_package_name "$name"; then + to_remove+=("$name") fi + done - echo -e "${YELLOW}Policy cleanup:${NC} Removing blocked installed packages: ${BOLD}${to_remove[*]}${NC}" >&2 - local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm) - "${remove_cmd[@]}" "${to_remove[@]}" - local rc=$? - if [[ $rc -ne 0 ]]; then - echo -e "${RED}Cleanup removal failed with exit code ${rc}.${NC}" >&2 - else - echo -e "${GREEN}Cleanup removal completed for: ${to_remove[*]}${NC}" >&2 - fi - return $rc + if [[ ${#to_remove[@]} -eq 0 ]]; then + return 0 + fi + + echo -e "${YELLOW}Policy cleanup:${NC} Removing blocked installed packages: ${BOLD}${to_remove[*]}${NC}" >&2 + local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm) + "${remove_cmd[@]}" "${to_remove[@]}" + local rc=$? + if [[ $rc -ne 0 ]]; then + echo -e "${RED}Cleanup removal failed with exit code ${rc}.${NC}" >&2 + else + echo -e "${GREEN}Cleanup removal completed for: ${to_remove[*]}${NC}" >&2 + fi + return $rc } # Function to check if user is trying to install packages that are always blocked function check_for_always_blocked() { - # Check if the command is an installation command - if [[ "$1" == "-S" || "$1" == "-Sy" || "$1" == "-Syu" || "$1" == "-Syyu" || "$1" == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present (like extra/ or community/) - local package_name="${arg##*/}" - if is_blocked_package_name "$package_name"; then - return 0 # Always blocked package found - fi - done - fi - return 1 # No always blocked package found + # Check if the command is an installation command + if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then + # Check all arguments + for arg in "$@"; do + # Strip repository prefix if present (like extra/ or community/) + local package_name="${arg##*/}" + if is_blocked_package_name "$package_name"; then + return 0 # Always blocked package found + fi + done + fi + return 1 # No always blocked package found } # Function to check if user is trying to install steam (challenge-eligible package) function check_for_steam() { - # List of packages that require challenge (only steam in this case) - local steam_packages=("steam") - - # Check if the command is an installation command - if [[ "$1" == "-S" || "$1" == "-Sy" || "$1" == "-Syu" || "$1" == "-Syyu" || "$1" == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present (like extra/ or community/) - local package_name="${arg##*/}" - - # Check if argument matches steam - for package in "${steam_packages[@]}"; do - if [[ "$arg" == "$package" || "$arg" == *"/$package-"* || "$arg" == *"/$package/"* || - "$arg" == *"/$package" || "$package_name" == "$package" ]]; then - return 0 # Steam package found - fi - done - done - fi - return 1 # No steam package found + # List of packages that require challenge (only steam in this case) + local steam_packages=("steam") + + # Check if the command is an installation command + if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then + # Check all arguments + for arg in "$@"; do + # Strip repository prefix if present (like extra/ or community/) + local package_name="${arg##*/}" + + # Check if argument matches steam + for package in "${steam_packages[@]}"; do + if [[ $arg == "$package" || $arg == *"/$package-"* || $arg == *"/$package/"* || + $arg == *"/$package" || $package_name == "$package" ]]; then + return 0 # Steam package found + fi + done + done + fi + return 1 # No steam package found } # Function to check if current day is a weekday (after 4PM Friday until midnight Sunday) function is_weekday() { - local day_of_week - day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7) - local hour - hour=$(date +%H) # %H gives hour in 24-hour format (00-23) - - # Monday through Thursday are always weekdays - if [[ $day_of_week -ge 1 && $day_of_week -le 4 ]]; then - return 0 # Is weekday - # Friday before 4PM is weekday, after 4PM is weekend - elif [[ $day_of_week -eq 5 ]]; then - if [[ $hour -lt 14 ]]; then - return 0 # Is weekday (Friday before 4PM) - else - return 1 # Is weekend (Friday after 4PM) - fi - # Saturday and Sunday are weekend + local day_of_week + day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7) + local hour + hour=$(date +%H) # %H gives hour in 24-hour format (00-23) + + # Monday through Thursday are always weekdays + if [[ $day_of_week -ge 1 && $day_of_week -le 4 ]]; then + return 0 # Is weekday + # Friday before 4PM is weekday, after 4PM is weekend + elif [[ $day_of_week -eq 5 ]]; then + if [[ $hour -lt 14 ]]; then + return 0 # Is weekday (Friday before 4PM) else - return 1 # Is weekend + return 1 # Is weekend (Friday after 4PM) fi + # Saturday and Sunday are weekend + else + return 1 # Is weekend + fi } # Function to prompt for solving a word unscrambling challenge (only for steam) function prompt_for_steam_challenge() { - echo -e "${YELLOW}WARNING: You are trying to install Steam.${NC}" - - # Check if it's a weekday and block completely - if is_weekday; then + echo -e "${YELLOW}WARNING: You are trying to install Steam.${NC}" + + # Check if it's a weekday and block completely + if is_weekday; then local day_name day_name=$(date +%A) - echo -e "${RED}Steam installation BLOCKED: Steam cannot be installed on weekdays.${NC}" - echo -e "${RED}Today is $day_name. Please try again on the weekend (Saturday or Sunday).${NC}" - return 1 - fi - - echo -e "${YELLOW}Weekend Steam challenge will begin shortly...${NC}" - - # Sleep for random 20-40 seconds - # sleep_duration=$((RANDOM % 20 + 20)) - sleep_duration=$((RANDOM % 20)) - sleep $sleep_duration - - # Define path to words.txt (in the same directory as the script) - script_dir="$(dirname "$(readlink -f "$0")")" - words_file="$script_dir/words.txt" - - # Check if words.txt exists - if [[ ! -f "$words_file" ]]; then - echo -e "${RED}Error: words.txt file not found at $words_file${NC}" - return 1 - fi - - # Choose a specific word length (5, 6, 7, or 8 characters) - # - word_length=5 - echo -e "${CYAN}Today's challenge: Words with ${word_length} letters${NC}" - - # Filter words by the specific chosen length and load random words - words_count=160 - mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n $words_count) - - # If we couldn't get enough words of the right length - if [[ ${#selected_words[@]} -lt $words_count ]]; then - echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}" - words_count=${#selected_words[@]} - if [[ $words_count -eq 0 ]]; then - echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}" - return 1 - fi - fi - - # Convert all words to uppercase - for i in "${!selected_words[@]}"; do - selected_words[$i]=$(echo "${selected_words[$i]}" | tr '[:lower:]' '[:upper:]') - done - - echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" - - # Display the words in a grid (4 columns) - for (( i=0; i/dev/null - wait $display_pid 2>/dev/null - echo # Add a newline after the timer - - # Check if read timed out - if [[ $read_status -ne 0 ]]; then - echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}" - return 1 - fi - - # Convert user input to uppercase and trim whitespaces - user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) - - if [[ "$user_input" == "$target_word" ]]; then - echo -e "${GREEN}Correct! Proceeding with installation...${NC}" - - # Add sleep after successful challenge completion (20-40 seconds) - # post_challenge_sleep=$((RANDOM % 20 + 20)) - post_challenge_sleep=$((RANDOM % 20)) - sleep $post_challenge_sleep - - return 0 - else - echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}" - return 1 + fi + + # Convert all words to uppercase + for i in "${!selected_words[@]}"; do + selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]') + done + + echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" + + # Display the words in a grid (4 columns) + for ((i = 0; i < words_count; i++)); do + printf "${BLUE}%-15s${NC}" "${selected_words[i]}" + if (((i + 1) % 4 == 0)); then + echo "" fi + done + + # Select a random word to scramble (already in uppercase) + target_index=$((RANDOM % words_count)) + target_word="${selected_words[target_index]}" + + # Scramble the word + scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n') + + # Ensure scrambled word is different from original + if [[ $scrambled_word == "$target_word" ]]; then + # Use simple reversal as fallback + scrambled_word=$(echo "$target_word" | rev) + fi + + echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}" + echo -e "${YELLOW}Unscramble the word to proceed with installation (you have 2 minutes):${NC}" + + # Set up a background process to display the timer + ( + start_time=$(date +%s) + while true; do + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + remaining=$((60 - elapsed)) + + if [[ $remaining -le 0 ]]; then + echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} " + break + fi + + echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} " + sleep 1 + done + ) & + display_pid=$! + + # Read user input with timeout + read -t 60 -r user_input + read_status=$? + + # Kill the timer display + kill "$display_pid" 2> /dev/null + wait "$display_pid" 2> /dev/null + echo # Add a newline after the timer + + # Check if read timed out + if [[ $read_status -ne 0 ]]; then + echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}" + return 1 + fi + + # Convert user input to uppercase and trim whitespaces + user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) + + if [[ $user_input == "$target_word" ]]; then + echo -e "${GREEN}Correct! Proceeding with installation...${NC}" + + # Add sleep after successful challenge completion (20-40 seconds) + # post_challenge_sleep=$((RANDOM % 20 + 20)) + post_challenge_sleep=$((RANDOM % 20)) + sleep "$post_challenge_sleep" + + return 0 + else + echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}" + return 1 + fi } # Function to prompt for solving a word unscrambling challenge (for virtualbox - always active) +# shellcheck disable=SC2329 # Invoked dynamically when matching VirtualBox packages function prompt_for_virtualbox_challenge() { - echo -e "${YELLOW}WARNING: You are trying to install VirtualBox.${NC}" - echo -e "${YELLOW}VirtualBox challenge will begin shortly...${NC}" - - # Sleep for random 10-30 seconds - sleep_duration=$((RANDOM % 20 + 10)) - sleep $sleep_duration - - # Define path to words.txt (in the same directory as the script) - script_dir="$(dirname "$(readlink -f "$0")")" - words_file="$script_dir/words.txt" - - # Check if words.txt exists - if [[ ! -f "$words_file" ]]; then - echo -e "${RED}Error: words.txt file not found at $words_file${NC}" - return 1 - fi - - # Choose a specific word length (6, 7, or 8 characters for VirtualBox) - word_length=6 - echo -e "${CYAN}VirtualBox challenge: Words with ${word_length} letters${NC}" - - # Filter words by the specific chosen length and load random words - words_count=120 - mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n $words_count) - - # If we couldn't get enough words of the right length - if [[ ${#selected_words[@]} -lt $words_count ]]; then - echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}" - words_count=${#selected_words[@]} - if [[ $words_count -eq 0 ]]; then - echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}" - return 1 - fi - fi - - # Convert all words to uppercase - for i in "${!selected_words[@]}"; do - selected_words[$i]=$(echo "${selected_words[$i]}" | tr '[:lower:]' '[:upper:]') - done - - echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" - - # Display the words in a grid (4 columns) - for (( i=0; i/dev/null - wait $display_pid 2>/dev/null - echo # Add a newline after the timer - - # Check if read timed out - if [[ $read_status -ne 0 ]]; then - echo -e "${RED}Time's up! VirtualBox challenge failed. The correct word was '$target_word'.${NC}" - return 1 - fi - - # Convert user input to uppercase and trim whitespaces - user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) - - if [[ "$user_input" == "$target_word" ]]; then - echo -e "${GREEN}Correct! Proceeding with VirtualBox installation...${NC}" - - # Add sleep after successful challenge completion (15-35 seconds) - post_challenge_sleep=$((RANDOM % 20 + 15)) - sleep $post_challenge_sleep - - return 0 - else - echo -e "${RED}Incorrect answer. VirtualBox installation aborted. The correct word was '$target_word'.${NC}" - return 1 + fi + + # Convert all words to uppercase + for i in "${!selected_words[@]}"; do + selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]') + done + + echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" + + # Display the words in a grid (4 columns) + for ((i = 0; i < words_count; i++)); do + printf "${BLUE}%-15s${NC}" "${selected_words[i]}" + if (((i + 1) % 4 == 0)); then + echo "" fi + done + + # Select a random word to scramble (already in uppercase) + target_index=$((RANDOM % words_count)) + target_word="${selected_words[target_index]}" + + # Scramble the word + scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n') + + # Ensure scrambled word is different from original + if [[ $scrambled_word == "$target_word" ]]; then + # Use simple reversal as fallback + scrambled_word=$(echo "$target_word" | rev) + fi + + echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}" + echo -e "${YELLOW}Unscramble the word to proceed with VirtualBox installation (you have 90 seconds):${NC}" + + # Set up a background process to display the timer + ( + start_time=$(date +%s) + while true; do + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + remaining=$((90 - elapsed)) + + if [[ $remaining -le 0 ]]; then + echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} " + break + fi + + echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} " + sleep 1 + done + ) & + display_pid=$! + + # Read user input with timeout (90 seconds for VirtualBox) + read -t 90 -r user_input + read_status=$? + + # Kill the timer display + kill "$display_pid" 2> /dev/null + wait "$display_pid" 2> /dev/null + echo # Add a newline after the timer + + # Check if read timed out + if [[ $read_status -ne 0 ]]; then + echo -e "${RED}Time's up! VirtualBox challenge failed. The correct word was '$target_word'.${NC}" + return 1 + fi + + # Convert user input to uppercase and trim whitespaces + user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) + + if [[ $user_input == "$target_word" ]]; then + echo -e "${GREEN}Correct! Proceeding with VirtualBox installation...${NC}" + + # Add sleep after successful challenge completion (15-35 seconds) + post_challenge_sleep=$((RANDOM % 20 + 15)) + sleep "$post_challenge_sleep" + + return 0 + else + echo -e "${RED}Incorrect answer. VirtualBox installation aborted. The correct word was '$target_word'.${NC}" + return 1 + fi } # Check for wrapper-specific commands -if [[ "$1" == "--help-wrapper" ]]; then - show_help - exit 0 +if [[ $1 == "--help-wrapper" ]]; then + show_help + exit 0 fi # Before any pacman action, ensure maintenance services exist @@ -666,19 +667,18 @@ ensure_periodic_maintenance # Check for always blocked packages first (highest priority) if check_for_always_blocked "$@"; then - echo -e "${RED}Installation BLOCKED: This package is permanently restricted and cannot be installed.${NC}" - echo -e "${RED}Package installation has been denied by system policy.${NC}" - # Regardless of the attempted action, enforce cleanup of any installed blocked packages - remove_installed_blocked_packages "$@" - exit 1 + echo -e "${RED}Installation BLOCKED: This package is permanently restricted and cannot be installed.${NC}" + echo -e "${RED}Package installation has been denied by system policy.${NC}" + # Regardless of the attempted action, enforce cleanup of any installed blocked packages + remove_installed_blocked_packages "$@" + exit 1 fi # Check for steam (challenge-eligible package) if check_for_steam "$@"; then - prompt_for_steam_challenge - if [[ $? -ne 0 ]]; then - exit 1 - fi + if ! prompt_for_steam_challenge; then + exit 1 + fi fi # Display operation @@ -692,19 +692,19 @@ start_time=$(date +%s) # Execute the real pacman command (with /etc/hosts guard handling) if needs_unlock "$@"; then - pre_unlock_hosts + pre_unlock_hosts fi # Handle a possible stale DB lock before executing if ! check_and_handle_db_lock "$@"; then - exit 1 + exit 1 fi "$PACMAN_BIN" "$@" exit_code=$? if needs_unlock "$@"; then - post_relock_hosts + post_relock_hosts fi # Record end time for statistics @@ -713,21 +713,21 @@ duration=$((end_time - start_time)) # Display results if [ $exit_code -eq 0 ]; then - echo -e "${GREEN}Command completed successfully in ${duration}s.${NC}" >&2 + echo -e "${GREEN}Command completed successfully in ${duration}s.${NC}" >&2 else - echo -e "${RED}Command failed with exit code ${exit_code}.${NC}" >&2 + echo -e "${RED}Command failed with exit code ${exit_code}.${NC}" >&2 fi # After any operation, remove installed blocked packages as part of policy enforcement remove_installed_blocked_packages "$@" # Display some helpful tips depending on the operation -if [[ "$1" == "-S" || "$1" == "-S "* ]] && [ $exit_code -eq 0 ]; then - echo -e "${CYAN}Tip:${NC} You may need to log out or restart to use some newly installed software." +if [[ $1 == "-S" || $1 == "-S "* ]] && [ $exit_code -eq 0 ]; then + echo -e "${CYAN}Tip:${NC} You may need to log out or restart to use some newly installed software." fi -if [[ "$1" == "-Syu" || "$1" == "-Syyu" ]] && [ $exit_code -eq 0 ]; then - echo -e "${CYAN}Tip:${NC} Consider restarting after major updates." +if [[ $1 == "-Syu" || $1 == "-Syyu" ]] && [ $exit_code -eq 0 ]; then + echo -e "${CYAN}Tip:${NC} Consider restarting after major updates." fi -exit $exit_code \ No newline at end of file +exit $exit_code diff --git a/scripts/digital_wellbeing/pc_startup_visual_status.sh b/scripts/digital_wellbeing/pc_startup_visual_status.sh index cfd5f55..aead3a4 100755 --- a/scripts/digital_wellbeing/pc_startup_visual_status.sh +++ b/scripts/digital_wellbeing/pc_startup_visual_status.sh @@ -24,254 +24,262 @@ BELL="🔔" # Function to draw a box around text draw_box() { - local text="$1" - local width=${#text} - local padding=2 - local total_width=$((width + padding * 2)) - - # Top border - printf "┌" - printf "─%.0s" $(seq 1 $total_width) - printf "┐\n" - - # Content with padding - printf "│%*s%s%*s│\n" $padding "" "$text" $padding "" - - # Bottom border - printf "└" - printf "─%.0s" $(seq 1 $total_width) - printf "┘\n" + local text="$1" + local width=${#text} + local padding=2 + local total_width=$((width + padding * 2)) + + # Top border + printf "┌" + printf "─%.0s" $(seq 1 $total_width) + printf "┐\n" + + # Content with padding + printf "│%*s%s%*s│\n" $padding "" "$text" $padding "" + + # Bottom border + printf "└" + printf "─%.0s" $(seq 1 $total_width) + printf "┘\n" } # Function to show current day status show_day_status() { - local day_of_week=$(date +%u) - local day_name=$(date +%A) - local today=$(date +%Y-%m-%d) - - printf "${BLUE}${CALENDAR} Day Status${NC}\n" - printf "═══════════════\n" - - # Show all days with status - local days=("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday") - local monitored=(1 0 0 0 1 1 1) # 1=monitored, 0=not monitored - - for i in {0..6}; do - local day_num=$((i + 1)) - if [[ $day_num -eq 7 ]]; then day_num=0; fi # Sunday is 0 in some contexts - - if [[ ${monitored[$i]} -eq 1 ]]; then - if [[ $day_of_week -eq $((i + 1)) ]] || [[ $day_of_week -eq 7 && $i -eq 6 ]]; then - printf "${GREEN}${CHECK} ${days[$i]} (TODAY - MONITORED)${NC}\n" - else - printf "${CYAN}${CHECK} ${days[$i]} (monitored)${NC}\n" - fi - else - if [[ $day_of_week -eq $((i + 1)) ]]; then - printf "${GRAY}○ ${days[$i]} (TODAY - not monitored)${NC}\n" - else - printf "${GRAY}○ ${days[$i]}${NC}\n" - fi - fi - done - - printf "\n" + local day_of_week + day_of_week=$(date +%u) + + printf '%s%s Day Status%s\n' "$BLUE" "$CALENDAR" "$NC" + printf '═══════════════\n' + + # Show all days with status + local days=("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday") + local monitored=(1 0 0 0 1 1 1) # 1=monitored, 0=not monitored + + for i in {0..6}; do + local day_num=$((i + 1)) + if [[ $day_num -eq 7 ]]; then day_num=0; fi # Sunday is 0 in some contexts + + if [[ ${monitored[$i]} -eq 1 ]]; then + if [[ $day_of_week -eq $((i + 1)) ]] || [[ $day_of_week -eq 7 && $i -eq 6 ]]; then + printf '%s%s %s (TODAY - MONITORED)%s\n' "$GREEN" "$CHECK" "${days[$i]}" "$NC" + else + printf '%s%s %s (monitored)%s\n' "$CYAN" "$CHECK" "${days[$i]}" "$NC" + fi + else + if [[ $day_of_week -eq $((i + 1)) ]]; then + printf '%s○ %s (TODAY - not monitored)%s\n' "$GRAY" "${days[$i]}" "$NC" + else + printf '%s○ %s%s\n' "$GRAY" "${days[$i]}" "$NC" + fi + fi + done + + printf "\n" } # Function to show time window status show_time_status() { - local current_hour=$(date +%H) - local current_minute=$(date +%M) - local current_hour_num=$((10#$current_hour)) - - printf "${YELLOW}${CLOCK} Time Window Status${NC}\n" - printf "═══════════════════════\n" - - # Show 24-hour timeline with window highlighted - printf "Timeline (24-hour format):\n" - printf "00 01 02 03 04 " - printf "${GREEN}05 06 07${NC} " - printf "08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n" - printf " " - printf "${GREEN}▲─────▲${NC}\n" - printf " " - printf "${GREEN}Expected Window${NC}\n" - - # Current time indicator - printf "\nCurrent time: ${WHITE}%02d:%s${NC}\n" $current_hour_num "$current_minute" - - if [[ $current_hour_num -ge 5 && $current_hour_num -lt 8 ]]; then - printf "Status: ${GREEN}${CHECK} Within expected window (5AM-8AM)${NC}\n" - else - printf "Status: ${YELLOW}○ Outside expected window${NC}\n" - fi - - printf "\n" + local current_hour current_minute current_hour_num + current_hour=$(date +%H) + current_minute=$(date +%M) + current_hour_num=$((10#$current_hour)) + + printf '%s%s Time Window Status%s\n' "$YELLOW" "$CLOCK" "$NC" + printf '═══════════════════════\n' + + # Show 24-hour timeline with window highlighted + printf 'Timeline (24-hour format):\n' + printf '00 01 02 03 04 ' + printf '%s05 06 07%s ' "$GREEN" "$NC" + printf '08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n' + printf ' ' + printf '%s▲─────▲%s\n' "$GREEN" "$NC" + printf ' ' + printf '%sExpected Window%s\n' "$GREEN" "$NC" + + # Current time indicator + printf '\nCurrent time: %s%02d:%s%s\n' "$WHITE" "$current_hour_num" "$current_minute" "$NC" + + if [[ $current_hour_num -ge 5 && $current_hour_num -lt 8 ]]; then + printf 'Status: %s%s Within expected window (5AM-8AM)%s\n' "$GREEN" "$CHECK" "$NC" + else + printf 'Status: %s○ Outside expected window%s\n' "$YELLOW" "$NC" + fi + + printf '\n' } # Function to show boot time analysis show_boot_analysis() { - printf "${PURPLE}${COMPUTER} Boot Time Analysis${NC}\n" - printf "═══════════════════════\n" - - # Get boot time - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - local boot_date=$(echo "$boot_time" | cut -d' ' -f1) - local boot_time_only=$(echo "$boot_time" | cut -d' ' -f2) - local boot_hour=$(echo "$boot_time_only" | cut -d':' -f1) - local boot_hour_num=$((10#$boot_hour)) - local today=$(date +%Y-%m-%d) - - printf "System boot time: ${WHITE}$boot_time${NC}\n" - - if [[ "$boot_date" == "$today" ]]; then - printf "Boot date: ${GREEN}${CHECK} Today${NC}\n" - - if [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then - printf "Boot window: ${GREEN}${CHECK} Within expected window (5AM-8AM)${NC}\n" - printf "Status: ${GREEN}${CHECK} COMPLIANT${NC}\n" - else - printf "Boot window: ${RED}${CROSS} Outside expected window${NC}\n" - printf "Status: ${RED}${WARNING} NON-COMPLIANT${NC}\n" - fi + printf '%s%s Boot Time Analysis%s\n' "$PURPLE" "$COMPUTER" "$NC" + printf '═══════════════════════\n' + + # Get boot time + local uptime_seconds boot_time boot_date boot_time_only boot_hour boot_hour_num today + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") + boot_date=$(echo "$boot_time" | cut -d' ' -f1) + boot_time_only=$(echo "$boot_time" | cut -d' ' -f2) + boot_hour=$(echo "$boot_time_only" | cut -d':' -f1) + boot_hour_num=$((10#$boot_hour)) + today=$(date +%Y-%m-%d) + + printf 'System boot time: %s%s%s\n' "$WHITE" "$boot_time" "$NC" + + if [[ $boot_date == "$today" ]]; then + printf 'Boot date: %s%s Today%s\n' "$GREEN" "$CHECK" "$NC" + + if [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then + printf 'Boot window: %s%s Within expected window (5AM-8AM)%s\n' "$GREEN" "$CHECK" "$NC" + printf 'Status: %s%s COMPLIANT%s\n' "$GREEN" "$CHECK" "$NC" else - printf "Boot date: ${YELLOW}○ Not today ($boot_date)${NC}\n" - printf "Status: ${YELLOW}○ System was not booted today${NC}\n" + printf 'Boot window: %s%s Outside expected window%s\n' "$RED" "$CROSS" "$NC" + printf 'Status: %s%s NON-COMPLIANT%s\n' "$RED" "$WARNING" "$NC" fi - - printf "\n" + else + printf 'Boot date: %s○ Not today (%s)%s\n' "$YELLOW" "$boot_date" "$NC" + printf 'Status: %s○ System was not booted today%s\n' "$YELLOW" "$NC" + fi + + printf '\n' } # Function to show monitoring system status show_system_status() { - printf "${CYAN}${BELL} Monitoring System${NC}\n" - printf "═══════════════════════\n" - - # Check if timer exists and is enabled - if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then - printf "Service: ${GREEN}${CHECK} ENABLED${NC}\n" - - if systemctl is-active pc-startup-monitor.timer &>/dev/null; then - printf "Timer: ${GREEN}${CHECK} ACTIVE${NC}\n" - else - printf "Timer: ${RED}${CROSS} INACTIVE${NC}\n" - fi - - # Show next check time - local next_check=$(systemctl list-timers pc-startup-monitor.timer --no-pager 2>/dev/null | grep pc-startup-monitor | awk '{print $1, $2, $3}' || echo "Not scheduled") - printf "Next check: ${WHITE}$next_check${NC}\n" - + printf '%s%s Monitoring System%s\n' "$CYAN" "$BELL" "$NC" + printf '═══════════════════════\n' + + # Check if timer exists and is enabled + if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then + printf 'Service: %s%s ENABLED%s\n' "$GREEN" "$CHECK" "$NC" + + if systemctl is-active pc-startup-monitor.timer &> /dev/null; then + printf 'Timer: %s%s ACTIVE%s\n' "$GREEN" "$CHECK" "$NC" else - printf "Service: ${RED}${CROSS} NOT ENABLED${NC}\n" - printf "Timer: ${RED}${CROSS} NOT ACTIVE${NC}\n" + printf 'Timer: %s%s INACTIVE%s\n' "$RED" "$CROSS" "$NC" fi - - printf "\n" + + # Show next check time + local next_check + next_check=$(systemctl list-timers pc-startup-monitor.timer --no-pager 2> /dev/null | grep pc-startup-monitor | awk '{print $1, $2, $3}' || echo "Not scheduled") + printf 'Next check: %s%s%s\n' "$WHITE" "$next_check" "$NC" + + else + printf 'Service: %s%s NOT ENABLED%s\n' "$RED" "$CROSS" "$NC" + printf 'Timer: %s%s NOT ACTIVE%s\n' "$RED" "$CROSS" "$NC" + fi + + printf '\n' } # Function to show overall compliance status show_compliance_overview() { - local day_of_week=$(date +%u) - local current_hour=$(date +%H) - local current_hour_num=$((10#$current_hour)) - - # Check if today is monitored - local is_monitored=false - if [[ "$day_of_week" == "1" ]] || [[ "$day_of_week" == "5" ]] || [[ "$day_of_week" == "6" ]] || [[ "$day_of_week" == "7" ]]; then - is_monitored=true - fi - - printf "${WHITE}" - draw_box "COMPLIANCE OVERVIEW" - printf "${NC}\n" - - if [[ "$is_monitored" == true ]]; then - printf "Today: ${GREEN}${CHECK} Monitored day${NC}\n" - - # Check current compliance - if [[ $current_hour_num -ge 5 && $current_hour_num -lt 8 ]]; then - printf "Current status: ${GREEN}${CHECK} PC is on during expected window${NC}\n" - printf "Action needed: ${GREEN}None - currently compliant${NC}\n" - else - # Check if booted in window - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - local boot_date=$(echo "$boot_time" | cut -d' ' -f1) - local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) - local boot_hour_num=$((10#$boot_hour)) - local today=$(date +%Y-%m-%d) - - if [[ "$boot_date" == "$today" ]] && [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then - printf "Current status: ${GREEN}${CHECK} PC was booted in expected window${NC}\n" - printf "Action needed: ${GREEN}None - compliant${NC}\n" - else - printf "Current status: ${RED}${WARNING} PC was NOT booted in expected window${NC}\n" - printf "Action needed: ${YELLOW}Warning will be shown at 8:30 AM${NC}\n" - fi - fi + local day_of_week current_hour current_hour_num + day_of_week=$(date +%u) + current_hour=$(date +%H) + current_hour_num=$((10#$current_hour)) + + # Check if today is monitored + local is_monitored=false + if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then + is_monitored=true + fi + + printf '%s' "$WHITE" + draw_box "COMPLIANCE OVERVIEW" + printf '%s\n' "$NC" + + if [[ $is_monitored == true ]]; then + printf 'Today: %s%s Monitored day%s\n' "$GREEN" "$CHECK" "$NC" + + # Check current compliance + if [[ $current_hour_num -ge 5 && $current_hour_num -lt 8 ]]; then + printf 'Current status: %s%s PC is on during expected window%s\n' "$GREEN" "$CHECK" "$NC" + printf 'Action needed: %sNone - currently compliant%s\n' "$GREEN" "$NC" else - printf "Today: ${GRAY}○ Not a monitored day${NC}\n" - printf "Current status: ${GRAY}No monitoring required${NC}\n" - printf "Action needed: ${GRAY}None${NC}\n" + # Check if booted in window + local uptime_seconds boot_time boot_date boot_hour boot_hour_num today + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") + boot_date=$(echo "$boot_time" | cut -d' ' -f1) + boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) + boot_hour_num=$((10#$boot_hour)) + today=$(date +%Y-%m-%d) + + if [[ $boot_date == "$today" ]] && [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then + printf 'Current status: %s%s PC was booted in expected window%s\n' "$GREEN" "$CHECK" "$NC" + printf 'Action needed: %sNone - compliant%s\n' "$GREEN" "$NC" + else + printf 'Current status: %s%s PC was NOT booted in expected window%s\n' "$RED" "$WARNING" "$NC" + printf 'Action needed: %sWarning will be shown at 8:30 AM%s\n' "$YELLOW" "$NC" + fi fi - - printf "\n" + else + printf 'Today: %s○ Not a monitored day%s\n' "$GRAY" "$NC" + printf 'Current status: %sNo monitoring required%s\n' "$GRAY" "$NC" + printf 'Action needed: %sNone%s\n' "$GRAY" "$NC" + fi + + printf '\n' } # Function to show recent activity show_recent_activity() { - printf "${GRAY}📋 Recent Activity${NC}\n" - printf "════════════════\n" - - # Show last 5 log entries - local logs=$(journalctl -t pc-startup-monitor --no-pager -n 5 --output=short 2>/dev/null || echo "No logs found") - - if [[ "$logs" == "No logs found" ]]; then - printf "${GRAY}No recent monitoring activity${NC}\n" - else - echo "$logs" | while IFS= read -r line; do - if [[ $line == *"WARNING"* ]]; then - printf "${RED}$line${NC}\n" - elif [[ $line == *"compliance OK"* ]]; then - printf "${GREEN}$line${NC}\n" - else - printf "${GRAY}$line${NC}\n" - fi - done - fi - - printf "\n" + printf '%s📋 Recent Activity%s\n' "$GRAY" "$NC" + printf '════════════════\n' + + # Show last 5 log entries + local logs + logs=$(journalctl -t pc-startup-monitor --no-pager -n 5 --output=short 2> /dev/null || echo "No logs found") + + if [[ $logs == "No logs found" ]]; then + printf '%sNo recent monitoring activity%s\n' "$GRAY" "$NC" + else + echo "$logs" | while IFS= read -r line; do + if [[ $line == *"WARNING"* ]]; then + printf '%s%s%s\n' "$RED" "$line" "$NC" + elif [[ $line == *"compliance OK"* ]]; then + printf '%s%s%s\n' "$GREEN" "$line" "$NC" + else + printf '%s%s%s\n' "$GRAY" "$line" "$NC" + fi + done + fi + + printf '\n' } # Main display function main() { - clear - - # Header - printf "${BLUE}" - draw_box "PC STARTUP MONITOR - VISUAL STATUS" - printf "${NC}\n\n" - - printf "${WHITE}Current Date/Time: $(date)${NC}\n" - printf "${WHITE}System Uptime: $(uptime -p)${NC}\n\n" - - # Show all status sections - show_day_status - show_time_status - show_boot_analysis - show_system_status - show_compliance_overview - show_recent_activity - - # Footer with commands - printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n" - printf "${WHITE}Commands:${NC}\n" - printf " ${CYAN}sudo pc-startup-monitor-manager.sh status${NC} - Show system status\n" - printf " ${CYAN}sudo pc-startup-monitor-manager.sh test${NC} - Test monitor now\n" - printf " ${CYAN}sudo pc-startup-monitor-manager.sh logs${NC} - View detailed logs\n" - printf " ${CYAN}$0${NC} - Show this visual status\n" - printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n" + clear + + # Header + printf '%s' "$BLUE" + draw_box "PC STARTUP MONITOR - VISUAL STATUS" + printf '%s\n\n' "$NC" + + local current_datetime system_uptime + current_datetime=$(date) + system_uptime=$(uptime -p) + printf '%sCurrent Date/Time: %s%s\n' "$WHITE" "$current_datetime" "$NC" + printf '%sSystem Uptime: %s%s\n\n' "$WHITE" "$system_uptime" "$NC" + + # Show all status sections + show_day_status + show_time_status + show_boot_analysis + show_system_status + show_compliance_overview + show_recent_activity + + # Footer with commands + printf '%s═══════════════════════════════════════════════════════════════%s\n' "$BLUE" "$NC" + printf '%sCommands:%s\n' "$WHITE" "$NC" + printf ' %s%s%s - Show system status\n' "$CYAN" "sudo pc-startup-monitor-manager.sh status" "$NC" + printf ' %s%s%s - Test monitor now\n' "$CYAN" "sudo pc-startup-monitor-manager.sh test" "$NC" + printf ' %s%s%s - View detailed logs\n' "$CYAN" "sudo pc-startup-monitor-manager.sh logs" "$NC" + printf ' %s%s%s - Show this visual status\n' "$CYAN" "$0" "$NC" + printf '%s═══════════════════════════════════════════════════════════════%s\n' "$BLUE" "$NC" } # Run main function diff --git a/scripts/digital_wellbeing/remove_guest_mode.sh b/scripts/digital_wellbeing/remove_guest_mode.sh index e975f94..3e875f8 100755 --- a/scripts/digital_wellbeing/remove_guest_mode.sh +++ b/scripts/digital_wellbeing/remove_guest_mode.sh @@ -12,10 +12,10 @@ SCRIPT_NAME=$(basename "$0") UNDO=false for arg in "$@"; do - case "$arg" in - --undo) UNDO=true ;; - -h|--help) - cat <"$tmp" - install -m 0644 "$tmp" "$file" - rm -f "$tmp" + mkdir -p "$target_dir" + # Write atomically + local tmp + tmp=$(mktemp) + printf '%s +' "$POLICY_JSON" > "$tmp" + install -m 0644 "$tmp" "$file" + rm -f "$tmp" } remove_policy() { - local target_dir="$1"; shift - local file="$target_dir/$POLICY_FILENAME" + local target_dir="$1" + shift + local file="$target_dir/$POLICY_FILENAME" - if [[ -f "$file" ]]; then - echo "[remove] $file" - rm -f -- "$file" - else - echo "[skip] $file (not present)" - fi + if [[ -f $file ]]; then + echo "[remove] $file" + rm -f -- "$file" + else + echo "[skip] $file (not present)" + fi } changed_any=false for key in "${!INSTALLED_KEYS[@]}"; do - # If we somehow lack candidate dirs for a key, skip gracefully - if [[ -z "${CANDIDATE_DIRS[$key]:-}" ]]; then - echo "[warn] No known policy directories for '$key'; skipping." - continue - fi + # If we somehow lack candidate dirs for a key, skip gracefully + if [[ -z ${CANDIDATE_DIRS[$key]:-} ]]; then + echo "[warn] No known policy directories for '$key'; skipping." + continue + fi - target_dir=$(choose_target_dir "$key") + target_dir=$(choose_target_dir "$key") - if [[ "$UNDO" == true ]]; then - remove_policy "$target_dir" - else - apply_policy "$target_dir" - fi + if [[ $UNDO == true ]]; then + remove_policy "$target_dir" + else + apply_policy "$target_dir" + fi - changed_any=true + changed_any=true done -if [[ "$changed_any" == false ]]; then - echo "[info] Nothing to do." +if [[ $changed_any == false ]]; then + echo "[info] Nothing to do." fi -if [[ "$UNDO" == true ]]; then - echo "[done] Guest mode policy files removed where present. You may need to restart the browsers." +if [[ $UNDO == true ]]; then + echo "[done] Guest mode policy files removed where present. You may need to restart the browsers." else - echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers." - echo " If the Guest option still appears, it should be disabled/greyed out." + echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers." + echo " If the Guest option still appears, it should be disabled/greyed out." fi - diff --git a/scripts/digital_wellbeing/setup_midnight_shutdown.sh b/scripts/digital_wellbeing/setup_midnight_shutdown.sh index 7fd887e..cea2678 100755 --- a/scripts/digital_wellbeing/setup_midnight_shutdown.sh +++ b/scripts/digital_wellbeing/setup_midnight_shutdown.sh @@ -4,194 +4,190 @@ # Thursday-Sunday: Shutdown between 22:00-05:00 # Handles sudo privileges automatically -set -e # Exit on any error +set -e # Exit on any error # Function to show usage show_usage() { - echo "Day-Specific Auto-Shutdown Setup for Arch Linux" - echo "===============================================" - echo "Usage: $0 [enable|disable|status]" - echo "" - echo "Commands:" - echo " enable - Set up automatic shutdown with day-specific windows (default)" - echo " disable - Remove automatic shutdown" - echo " status - Show current status" - echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" - echo "" + echo "Day-Specific Auto-Shutdown Setup for Arch Linux" + echo "===============================================" + echo "Usage: $0 [enable|disable|status]" + echo "" + echo "Commands:" + echo " enable - Set up automatic shutdown with day-specific windows (default)" + echo " disable - Remove automatic shutdown" + echo " status - Show current status" + echo "" + echo "Shutdown Schedule:" + echo " Monday-Wednesday: 21:00-05:00" + echo " Thursday-Sunday: 22:00-05:00" + echo "" } # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to manage systemd services." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to manage systemd services." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Get the actual user (even when running with sudo) -if [[ -n "$SUDO_USER" ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" +if [[ -n $SUDO_USER ]]; then + ACTUAL_USER="$SUDO_USER" + USER_HOME="/home/$SUDO_USER" else - ACTUAL_USER="$USER" - USER_HOME="$HOME" + ACTUAL_USER="$USER" + USER_HOME="$HOME" fi # Function to disable and remove midnight shutdown disable_midnight_shutdown() { - echo "Disabling Day-Specific Auto-Shutdown" - echo "====================================" - echo "Current Date: $(date)" - echo "User: $ACTUAL_USER" - echo "" - - local timer_file="/etc/systemd/system/day-specific-shutdown.timer" - local service_file="/etc/systemd/system/day-specific-shutdown.service" - local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" - local check_script="/usr/local/bin/day-specific-shutdown-check.sh" - local removed_files=() - - # Stop and disable timer if it exists - if systemctl is-active day-specific-shutdown.timer &>/dev/null; then - echo "Stopping day-specific-shutdown timer..." - systemctl stop day-specific-shutdown.timer - echo "✓ Timer stopped" - fi - - if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then - echo "Disabling day-specific-shutdown timer..." - systemctl disable day-specific-shutdown.timer - echo "✓ Timer disabled" - fi - - # Remove timer file - if [[ -f "$timer_file" ]]; then - rm -f "$timer_file" - removed_files+=("$timer_file") - echo "✓ Removed timer file: $timer_file" - fi - - # Remove service file - if [[ -f "$service_file" ]]; then - rm -f "$service_file" - removed_files+=("$service_file") - echo "✓ Removed service file: $service_file" - fi - - # Remove management script - if [[ -f "$script_file" ]]; then - rm -f "$script_file" - removed_files+=("$script_file") - echo "✓ Removed management script: $script_file" - fi - - # Remove check script - if [[ -f "$check_script" ]]; then - rm -f "$check_script" - removed_files+=("$check_script") - echo "✓ Removed check script: $check_script" - fi - - # Reload systemd daemon - if [[ ${#removed_files[@]} -gt 0 ]]; then - echo "Reloading systemd daemon..." - systemctl daemon-reload - echo "✓ Systemd daemon reloaded" - fi - - echo "" - echo "=============================================" - echo "Day-Specific Auto-Shutdown Removal Complete" - echo "=============================================" - - if [[ ${#removed_files[@]} -gt 0 ]]; then - echo "Removed files:" - printf " %s\n" "${removed_files[@]}" - echo "" - echo "✓ Automatic day-specific shutdown has been completely disabled" - echo "✓ Your PC will no longer shutdown automatically" - else - echo "No day-specific shutdown configuration was found to remove." - fi - + echo "Disabling Day-Specific Auto-Shutdown" + echo "====================================" + echo "Current Date: $(date)" + echo "User: $ACTUAL_USER" + echo "" + + local timer_file="/etc/systemd/system/day-specific-shutdown.timer" + local service_file="/etc/systemd/system/day-specific-shutdown.service" + local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" + local check_script="/usr/local/bin/day-specific-shutdown-check.sh" + local removed_files=() + + # Stop and disable timer if it exists + if systemctl is-active day-specific-shutdown.timer &> /dev/null; then + echo "Stopping day-specific-shutdown timer..." + systemctl stop day-specific-shutdown.timer + echo "✓ Timer stopped" + fi + + if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then + echo "Disabling day-specific-shutdown timer..." + systemctl disable day-specific-shutdown.timer + echo "✓ Timer disabled" + fi + + # Remove timer file + if [[ -f $timer_file ]]; then + rm -f "$timer_file" + removed_files+=("$timer_file") + echo "✓ Removed timer file: $timer_file" + fi + + # Remove service file + if [[ -f $service_file ]]; then + rm -f "$service_file" + removed_files+=("$service_file") + echo "✓ Removed service file: $service_file" + fi + + # Remove management script + if [[ -f $script_file ]]; then + rm -f "$script_file" + removed_files+=("$script_file") + echo "✓ Removed management script: $script_file" + fi + + # Remove check script + if [[ -f $check_script ]]; then + rm -f "$check_script" + removed_files+=("$check_script") + echo "✓ Removed check script: $check_script" + fi + + # Reload systemd daemon + if [[ ${#removed_files[@]} -gt 0 ]]; then + echo "Reloading systemd daemon..." + systemctl daemon-reload + echo "✓ Systemd daemon reloaded" + fi + + echo "" + echo "=============================================" + echo "Day-Specific Auto-Shutdown Removal Complete" + echo "=============================================" + + if [[ ${#removed_files[@]} -gt 0 ]]; then + echo "Removed files:" + printf " %s\n" "${removed_files[@]}" echo "" + echo "✓ Automatic day-specific shutdown has been completely disabled" + echo "✓ Your PC will no longer shutdown automatically" + else + echo "No day-specific shutdown configuration was found to remove." + fi + + echo "" } # Function to show current status show_current_status() { - echo "Day-Specific Auto-Shutdown Status" - echo "=================================" - echo "Current Date: $(date)" - echo "User: $ACTUAL_USER" - echo "" - - local timer_exists=false - local service_exists=false - local script_exists=false - - # Check if files exist - if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then - timer_exists=true - echo "✓ Timer file exists" + echo "Day-Specific Auto-Shutdown Status" + echo "=================================" + echo "Current Date: $(date)" + echo "User: $ACTUAL_USER" + echo "" + + local timer_exists=false + + # Check if files exist + if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then + timer_exists=true + echo "✓ Timer file exists" + else + echo "✗ Timer file missing" + fi + + if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then + echo "✓ Service file exists" + else + echo "✗ Service file missing" + fi + + if [[ -f "/usr/local/bin/day-specific-shutdown-manager.sh" ]]; then + echo "✓ Management script exists" + else + echo "✗ Management script missing" + fi + + echo "" + + # Check systemd status + if $timer_exists; then + if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then + echo "✓ Timer is enabled" + if systemctl is-active day-specific-shutdown.timer &> /dev/null; then + echo "✓ Timer is active" + echo "" + echo "Next scheduled shutdown check:" + systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | grep day-specific-shutdown || echo "Timer information not available" + else + echo "✗ Timer is not active" + fi else - echo "✗ Timer file missing" + echo "✗ Timer is not enabled" fi - - if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then - service_exists=true - echo "✓ Service file exists" - else - echo "✗ Service file missing" - fi - - if [[ -f "/usr/local/bin/day-specific-shutdown-manager.sh" ]]; then - script_exists=true - echo "✓ Management script exists" - else - echo "✗ Management script missing" - fi - - echo "" - - # Check systemd status - if $timer_exists; then - if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then - echo "✓ Timer is enabled" - if systemctl is-active day-specific-shutdown.timer &>/dev/null; then - echo "✓ Timer is active" - echo "" - echo "Next scheduled shutdown check:" - systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | grep day-specific-shutdown || echo "Timer information not available" - else - echo "✗ Timer is not active" - fi - else - echo "✗ Timer is not enabled" - fi - else - echo "Status: NOT CONFIGURED" - fi - - echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" - echo "" + else + echo "Status: NOT CONFIGURED" + fi + + echo "" + echo "Shutdown Schedule:" + echo " Monday-Wednesday: 21:00-05:00" + echo " Thursday-Sunday: 22:00-05:00" + echo "" } # Function to create the shutdown service create_shutdown_service() { - echo "" - echo "1. Creating Systemd Shutdown Service..." - echo "======================================" - - local service_file="/etc/systemd/system/day-specific-shutdown.service" - - cat > "$service_file" << 'EOF' + echo "" + echo "1. Creating Systemd Shutdown Service..." + echo "======================================" + + local service_file="/etc/systemd/system/day-specific-shutdown.service" + + cat > "$service_file" << 'EOF' [Unit] Description=Automatic PC shutdown with day-specific time windows DefaultDependencies=false @@ -205,18 +201,18 @@ StandardOutput=journal StandardError=journal EOF - echo "✓ Created systemd service: $service_file" + echo "✓ Created systemd service: $service_file" } # Function to create the shutdown timer create_shutdown_timer() { - echo "" - echo "2. Creating Systemd Shutdown Timer..." - echo "===================================" - - local timer_file="/etc/systemd/system/day-specific-shutdown.timer" - - cat > "$timer_file" << 'EOF' + echo "" + echo "2. Creating Systemd Shutdown Timer..." + echo "===================================" + + local timer_file="/etc/systemd/system/day-specific-shutdown.timer" + + cat > "$timer_file" << 'EOF' [Unit] Description=Timer for automatic PC shutdown with day-specific windows Requires=day-specific-shutdown.service @@ -248,18 +244,18 @@ RandomizedDelaySec=0 WantedBy=timers.target EOF - echo "✓ Created systemd timer: $timer_file" + echo "✓ Created systemd timer: $timer_file" } # Function to create management script create_management_script() { - echo "" - echo "3. Creating Management Script..." - echo "==============================" - - local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" - - cat > "$script_file" << 'EOF' + echo "" + echo "3. Creating Management Script..." + echo "==============================" + + local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" + + cat > "$script_file" << 'EOF' #!/bin/bash # Day-Specific Auto-Shutdown Manager # Provides easy management of the day-specific shutdown feature @@ -322,19 +318,19 @@ case "$1" in esac EOF - chmod +x "$script_file" - echo "✓ Created management script: $script_file" + chmod +x "$script_file" + echo "✓ Created management script: $script_file" } # Function to create smart shutdown check script create_shutdown_check_script() { - echo "" - echo "4. Creating Smart Shutdown Check Script..." - echo "========================================" - - local check_script="/usr/local/bin/day-specific-shutdown-check.sh" - - cat > "$check_script" << 'EOF' + echo "" + echo "4. Creating Smart Shutdown Check Script..." + echo "========================================" + + local check_script="/usr/local/bin/day-specific-shutdown-check.sh" + + cat > "$check_script" << 'EOF' #!/bin/bash # Smart day-specific shutdown check script # Different shutdown windows based on day of week: @@ -401,206 +397,206 @@ else fi EOF - chmod +x "$check_script" - echo "✓ Created smart shutdown check script: $check_script" + chmod +x "$check_script" + echo "✓ Created smart shutdown check script: $check_script" } # Function to enable the timer enable_timer() { - echo "" - echo "5. Enabling Shutdown Timer..." - echo "============================" - - # Reload systemd daemon - systemctl daemon-reload - echo "✓ Reloaded systemd daemon" - - # Enable the timer - systemctl enable day-specific-shutdown.timer - echo "✓ Enabled day-specific-shutdown timer" - - # Start the timer - systemctl start day-specific-shutdown.timer - echo "✓ Started day-specific-shutdown timer" + echo "" + echo "5. Enabling Shutdown Timer..." + echo "============================" + + # Reload systemd daemon + systemctl daemon-reload + echo "✓ Reloaded systemd daemon" + + # Enable the timer + systemctl enable day-specific-shutdown.timer + echo "✓ Enabled day-specific-shutdown timer" + + # Start the timer + systemctl start day-specific-shutdown.timer + echo "✓ Started day-specific-shutdown timer" } # Function to test the setup test_setup() { - echo "" - echo "6. Testing Setup..." - echo "==================" - - echo "Service files:" - if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then - echo "✓ Service file exists" - else - echo "✗ Service file missing" - fi - - if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then - echo "✓ Timer file exists" - else - echo "✗ Timer file missing" - fi - - echo "" - echo "Timer status:" - if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then - echo "✓ Timer is enabled" - else - echo "✗ Timer is not enabled" - fi - - if systemctl is-active day-specific-shutdown.timer &>/dev/null; then - echo "✓ Timer is active" - else - echo "✗ Timer is not active" - fi - - echo "" - echo "Next scheduled checks:" - systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available" + echo "" + echo "6. Testing Setup..." + echo "==================" + + echo "Service files:" + if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then + echo "✓ Service file exists" + else + echo "✗ Service file missing" + fi + + if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then + echo "✓ Timer file exists" + else + echo "✗ Timer file missing" + fi + + echo "" + echo "Timer status:" + if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then + echo "✓ Timer is enabled" + else + echo "✗ Timer is not enabled" + fi + + if systemctl is-active day-specific-shutdown.timer &> /dev/null; then + echo "✓ Timer is active" + else + echo "✗ Timer is not active" + fi + + echo "" + echo "Next scheduled checks:" + systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available" } # Function to show final instructions show_instructions() { - echo "" - echo "=================================================" - echo "Day-Specific Auto-Shutdown Setup Complete" - echo "=================================================" - echo "Summary:" - echo "✓ Systemd service created (/etc/systemd/system/day-specific-shutdown.service)" - echo "✓ Systemd timer created (/etc/systemd/system/day-specific-shutdown.timer)" - echo "✓ Management script created (/usr/local/bin/day-specific-shutdown-manager.sh)" - echo "✓ Smart check script created (/usr/local/bin/day-specific-shutdown-check.sh)" - echo "✓ Timer enabled and started" - echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" - echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" - echo "" - echo "Management commands:" - echo " sudo day-specific-shutdown-manager.sh status - Check status" - echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs" - echo "" - echo "How it works:" - echo "• Timer checks every 30 minutes during potential shutdown windows" - echo "• Smart logic determines shutdown eligibility based on day and time" - echo "• Prevents accidental shutdowns outside designated time windows" - echo "" - echo "WARNING: This will automatically shutdown your PC during designated hours." - echo "Make sure to save your work before the shutdown windows!" - echo "" + echo "" + echo "=================================================" + echo "Day-Specific Auto-Shutdown Setup Complete" + echo "=================================================" + echo "Summary:" + echo "✓ Systemd service created (/etc/systemd/system/day-specific-shutdown.service)" + echo "✓ Systemd timer created (/etc/systemd/system/day-specific-shutdown.timer)" + echo "✓ Management script created (/usr/local/bin/day-specific-shutdown-manager.sh)" + echo "✓ Smart check script created (/usr/local/bin/day-specific-shutdown-check.sh)" + echo "✓ Timer enabled and started" + echo "" + echo "Shutdown Schedule:" + echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" + echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" + echo "" + echo "Management commands:" + echo " sudo day-specific-shutdown-manager.sh status - Check status" + echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs" + echo "" + echo "How it works:" + echo "• Timer checks every 30 minutes during potential shutdown windows" + echo "• Smart logic determines shutdown eligibility based on day and time" + echo "• Prevents accidental shutdowns outside designated time windows" + echo "" + echo "WARNING: This will automatically shutdown your PC during designated hours." + echo "Make sure to save your work before the shutdown windows!" + echo "" } # Function to prompt for confirmation confirm_setup() { - echo "" - echo "WARNING: Day-Specific Auto-Shutdown Confirmation" - echo "===============================================" - echo "This will set up your PC to automatically shutdown during specific time windows." - echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" - echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" - echo "" - echo "Important considerations:" - echo "- Any unsaved work will be lost during shutdown windows" - echo "- Running processes will be terminated" - echo "- Downloads/uploads in progress will be interrupted" - echo "- You'll need to manually power on your PC each day" - echo "- Timer checks every 30 minutes during potential shutdown windows" - echo "" - read -p "Do you want to proceed? (y/N): " confirm - - case "$confirm" in - [yY]|[yY][eE][sS]) - echo "Proceeding with setup..." - return 0 - ;; - *) - echo "Setup cancelled." - exit 0 - ;; - esac + echo "" + echo "WARNING: Day-Specific Auto-Shutdown Confirmation" + echo "===============================================" + echo "This will set up your PC to automatically shutdown during specific time windows." + echo "" + echo "Shutdown Schedule:" + echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" + echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" + echo "" + echo "Important considerations:" + echo "- Any unsaved work will be lost during shutdown windows" + echo "- Running processes will be terminated" + echo "- Downloads/uploads in progress will be interrupted" + echo "- You'll need to manually power on your PC each day" + echo "- Timer checks every 30 minutes during potential shutdown windows" + echo "" + read -r -p "Do you want to proceed? (y/N): " confirm + + case "$confirm" in + [yY] | [yY][eE][sS]) + echo "Proceeding with setup..." + return 0 + ;; + *) + echo "Setup cancelled." + exit 0 + ;; + esac } # Function to confirm disable confirm_disable() { - echo "" - echo "Disable Day-Specific Auto-Shutdown Confirmation" - echo "===============================================" - echo "This will completely remove the automatic day-specific shutdown configuration." - echo "" - echo "After disabling:" - echo "- Your PC will no longer shutdown automatically during any time windows" - echo "- All related systemd services and timers will be removed" - echo "- The management and check scripts will be deleted" - echo "" - read -p "Do you want to proceed with disabling? (y/N): " confirm - - case "$confirm" in - [yY]|[yY][eE][sS]) - echo "Proceeding with disable..." - return 0 - ;; - *) - echo "Disable cancelled." - exit 0 - ;; - esac + echo "" + echo "Disable Day-Specific Auto-Shutdown Confirmation" + echo "===============================================" + echo "This will completely remove the automatic day-specific shutdown configuration." + echo "" + echo "After disabling:" + echo "- Your PC will no longer shutdown automatically during any time windows" + echo "- All related systemd services and timers will be removed" + echo "- The management and check scripts will be deleted" + echo "" + read -r -p "Do you want to proceed with disabling? (y/N): " confirm + + case "$confirm" in + [yY] | [yY][eE][sS]) + echo "Proceeding with disable..." + return 0 + ;; + *) + echo "Disable cancelled." + exit 0 + ;; + esac } # Main execution flow for enable enable_midnight_shutdown() { - echo "Day-Specific Auto-Shutdown Setup for Arch Linux" - echo "===============================================" - echo "Current Date: $(date)" - echo "User: $ACTUAL_USER" - echo "Target user: $ACTUAL_USER" - echo "User home: $USER_HOME" - - # Confirm setup - confirm_setup - - # Create systemd files - create_shutdown_service - create_shutdown_timer - create_management_script - create_shutdown_check_script - - # Enable and start timer - enable_timer - - # Test setup - test_setup - - # Show instructions - show_instructions + echo "Day-Specific Auto-Shutdown Setup for Arch Linux" + echo "===============================================" + echo "Current Date: $(date)" + echo "User: $ACTUAL_USER" + echo "Target user: $ACTUAL_USER" + echo "User home: $USER_HOME" + + # Confirm setup + confirm_setup + + # Create systemd files + create_shutdown_service + create_shutdown_timer + create_management_script + create_shutdown_check_script + + # Enable and start timer + enable_timer + + # Test setup + test_setup + + # Show instructions + show_instructions } # Parse command line arguments case "${1:-enable}" in - "enable") - check_sudo "$@" - enable_midnight_shutdown - ;; - "disable") - check_sudo "$@" - confirm_disable - disable_midnight_shutdown - ;; - "status") - check_sudo "$@" - show_current_status - ;; - "help"|"-h"|"--help") - show_usage - ;; - *) - echo "Error: Unknown command '$1'" - echo "" - show_usage - exit 1 - ;; + "enable") + check_sudo "$@" + enable_midnight_shutdown + ;; + "disable") + check_sudo "$@" + confirm_disable + disable_midnight_shutdown + ;; + "status") + check_sudo "$@" + show_current_status + ;; + "help" | "-h" | "--help") + show_usage + ;; + *) + echo "Error: Unknown command '$1'" + echo "" + show_usage + exit 1 + ;; esac diff --git a/scripts/digital_wellbeing/setup_pc_startup_monitor.sh b/scripts/digital_wellbeing/setup_pc_startup_monitor.sh index 98c56fe..b94e8ad 100755 --- a/scripts/digital_wellbeing/setup_pc_startup_monitor.sh +++ b/scripts/digital_wellbeing/setup_pc_startup_monitor.sh @@ -3,59 +3,59 @@ # Checks if PC was turned on between 5AM-8AM on Monday, Friday, Saturday, Sunday # Handles sudo privileges automatically -set -e # Exit on any error +set -e # Exit on any error # Default to non-interactive mode INTERACTIVE_MODE=false # Parse command line arguments while [[ $# -gt 0 ]]; do - case $1 in - -i|--interactive) - INTERACTIVE_MODE=true - shift - ;; - -h|--help) - echo "Usage: $0 [OPTIONS]" - echo "Options:" - echo " -i, --interactive Enable interactive prompts (default: auto-yes)" - echo " -h, --help Show this help message" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Use -h or --help for usage information" - exit 1 - ;; - esac + case $1 in + -i | --interactive) + INTERACTIVE_MODE=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " -i, --interactive Enable interactive prompts (default: auto-yes)" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac done echo "PC Startup Time Monitor for Arch Linux" echo "======================================" echo "Current Date: $(date)" echo "User: ${SUDO_USER:-$USER}" -if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" +if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Mode: Interactive (prompts enabled)" else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" + echo "Mode: Automatic (auto-yes, use --interactive for prompts)" fi # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to access system logs and create services." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to access system logs and create services." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Get the actual user (even when running with sudo) -if [[ -n "$SUDO_USER" ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" +if [[ -n $SUDO_USER ]]; then + ACTUAL_USER="$SUDO_USER" + USER_HOME="/home/$SUDO_USER" else - ACTUAL_USER="$USER" - USER_HOME="$HOME" + ACTUAL_USER="$USER" + USER_HOME="$HOME" fi echo "Target user: $ACTUAL_USER" @@ -63,138 +63,147 @@ echo "User home: $USER_HOME" # Function to check if today is a monitored day is_monitored_day() { - local day_of_week=$(date +%u) # 1=Monday, 7=Sunday - - # Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7) - if [[ "$day_of_week" == "1" ]] || [[ "$day_of_week" == "5" ]] || [[ "$day_of_week" == "6" ]] || [[ "$day_of_week" == "7" ]]; then - return 0 # Yes, it's a monitored day - else - return 1 # No, it's not a monitored day - fi + local day_of_week + day_of_week=$(date +%u) # 1=Monday, 7=Sunday + + # Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7) + if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then + return 0 # Yes, it's a monitored day + else + return 1 # No, it's not a monitored day + fi } # Function to check if current time is between 5AM and 8AM is_current_time_in_window() { - local current_hour=$(date +%H) - local current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues - - if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then - return 0 # Yes, current time is in the 5AM-8AM window - else - return 1 # No, current time is outside the window - fi + local current_hour current_hour_num + current_hour=$(date +%H) + current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues + + if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then + return 0 # Yes, current time is in the 5AM-8AM window + else + return 1 # No, current time is outside the window + fi } # Function to check if PC was booted between 5AM-8AM today was_booted_in_window_today() { - local today=$(date +%Y-%m-%d) - local boot_time="" - - # Get the last boot time using multiple methods for reliability - if command -v uptime &>/dev/null; then - # Method 1: Calculate boot time from uptime - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - if [[ $uptime_seconds -gt 0 ]]; then - boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - fi + local today boot_time + today=$(date +%Y-%m-%d) + boot_time="" + + # Get the last boot time using multiple methods for reliability + if command -v uptime &> /dev/null; then + # Method 1: Calculate boot time from uptime + local uptime_seconds + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + if [[ $uptime_seconds -gt 0 ]]; then + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") fi - - # Method 2: Use systemd if available (fallback) - if [[ -z "$boot_time" ]] && command -v systemctl &>/dev/null; then - boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2>/dev/null || echo "") - if [[ -n "$boot_time" ]]; then - # This gives us relative time, need to calculate absolute time - local current_time=$(date +%s) - local uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S") - fi + fi + + # Method 2: Use systemd if available (fallback) + if [[ -z $boot_time ]] && command -v systemctl &> /dev/null; then + boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2> /dev/null || echo "") + if [[ -n $boot_time ]]; then + # This gives us relative time, need to calculate absolute time + local current_time uptime_sec + current_time=$(date +%s) + uptime_sec=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S") fi - - # Method 3: Use who -b (fallback) - if [[ -z "$boot_time" ]] && command -v who &>/dev/null; then - boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "") - if [[ -n "$boot_time" ]]; then - boot_time="$today $boot_time" - fi - fi - - # Method 4: Use /proc/uptime as final fallback - if [[ -z "$boot_time" ]]; then - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - fi - - echo "Boot time detected: $boot_time" - - # Check if boot time is from today - local boot_date=$(echo "$boot_time" | cut -d' ' -f1) - if [[ "$boot_date" != "$today" ]]; then - echo "PC was not booted today (boot date: $boot_date, today: $today)" - return 1 # Not booted today - fi - - # Extract hour from boot time - local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) - local boot_hour_num=$((10#$boot_hour)) # Convert to decimal - - echo "Boot hour: $boot_hour_num" - - # Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM) - if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then - echo "PC was booted in the expected window (5AM-8AM)" - return 0 # Yes, booted in window - else - echo "PC was NOT booted in the expected window (5AM-8AM)" - return 1 # No, not booted in window + fi + + # Method 3: Use who -b (fallback) + if [[ -z $boot_time ]] && command -v who &> /dev/null; then + boot_time=$(who -b | awk '{print $3, $4}' 2> /dev/null || echo "") + if [[ -n $boot_time ]]; then + boot_time="$today $boot_time" fi + fi + + # Method 4: Use /proc/uptime as final fallback + if [[ -z $boot_time ]]; then + local uptime_seconds + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0") + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") + fi + + echo "Boot time detected: $boot_time" + + # Check if boot time is from today + local boot_date + boot_date=$(echo "$boot_time" | cut -d' ' -f1) + if [[ $boot_date != "$today" ]]; then + echo "PC was not booted today (boot date: $boot_date, today: $today)" + return 1 # Not booted today + fi + + # Extract hour from boot time + local boot_hour boot_hour_num + boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) + boot_hour_num=$((10#$boot_hour)) # Convert to decimal + + echo "Boot hour: $boot_hour_num" + + # Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM) + if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then + echo "PC was booted in the expected window (5AM-8AM)" + return 0 # Yes, booted in window + else + echo "PC was NOT booted in the expected window (5AM-8AM)" + return 1 # No, not booted in window + fi } # Function to show notification/warning show_startup_warning() { - local day_name=$(date +%A) - local current_time=$(date +"%H:%M") - local today=$(date +%Y-%m-%d) - - echo "" - echo "⚠️ PC STARTUP TIME WARNING" - echo "==========================" - echo "Date: $today ($day_name)" - echo "Current time: $current_time" - echo "" - echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today," - echo "but it was not turned on during that time window." - echo "" - echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM" - echo "Actual: PC was turned on outside the expected window" - echo "" - - # Log the warning - logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today" - - # Try to show desktop notification if possible - if command -v notify-send &>/dev/null && [[ -n "$DISPLAY" ]]; then - if [[ $EUID -eq 0 ]]; then - # Running as root, send notification as user - sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true - else - notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true - fi + local day_name current_time today + day_name=$(date +%A) + current_time=$(date +"%H:%M") + today=$(date +%Y-%m-%d) + + echo "" + echo "⚠️ PC STARTUP TIME WARNING" + echo "==========================" + echo "Date: $today ($day_name)" + echo "Current time: $current_time" + echo "" + echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today," + echo "but it was not turned on during that time window." + echo "" + echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM" + echo "Actual: PC was turned on outside the expected window" + echo "" + + # Log the warning + logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today" + + # Try to show desktop notification if possible + if command -v notify-send &> /dev/null && [[ -n $DISPLAY ]]; then + if [[ $EUID -eq 0 ]]; then + # Running as root, send notification as user + sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true + else + notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true fi - - echo "This warning has been logged to the system journal." - echo "You can view startup logs with: journalctl -t pc-startup-monitor" - echo "" + fi + + echo "This warning has been logged to the system journal." + echo "You can view startup logs with: journalctl -t pc-startup-monitor" + echo "" } # Function to create the monitoring service create_monitoring_service() { - echo "" - echo "1. Creating PC Startup Monitor Service..." - echo "=======================================" - - local service_file="/etc/systemd/system/pc-startup-monitor.service" - - cat > "$service_file" << 'EOF' + echo "" + echo "1. Creating PC Startup Monitor Service..." + echo "=======================================" + + local service_file="/etc/systemd/system/pc-startup-monitor.service" + + cat > "$service_file" << 'EOF' [Unit] Description=PC Startup Time Monitor After=multi-user.target @@ -211,18 +220,18 @@ RemainAfterExit=true WantedBy=multi-user.target EOF - echo "✓ Created monitoring service: $service_file" + echo "✓ Created monitoring service: $service_file" } # Function to create the monitoring timer create_monitoring_timer() { - echo "" - echo "2. Creating PC Startup Monitor Timer..." - echo "=====================================" - - local timer_file="/etc/systemd/system/pc-startup-monitor.timer" - - cat > "$timer_file" << 'EOF' + echo "" + echo "2. Creating PC Startup Monitor Timer..." + echo "=====================================" + + local timer_file="/etc/systemd/system/pc-startup-monitor.timer" + + cat > "$timer_file" << 'EOF' [Unit] Description=Timer for PC startup monitoring Requires=pc-startup-monitor.service @@ -236,25 +245,26 @@ AccuracySec=1m WantedBy=timers.target EOF - echo "✓ Created monitoring timer: $timer_file" + echo "✓ Created monitoring timer: $timer_file" } # Function to create the main monitoring script create_monitoring_script() { - echo "" - echo "3. Creating PC Startup Monitor Script..." - echo "======================================" - - local script_file="/usr/local/bin/pc-startup-check.sh" - - cat > "$script_file" << 'EOF' + echo "" + echo "3. Creating PC Startup Monitor Script..." + echo "======================================" + + local script_file="/usr/local/bin/pc-startup-check.sh" + + cat > "$script_file" << 'EOF' #!/bin/bash # PC Startup Time Monitor Check Script # Monitors if PC was turned on during expected hours on specific days # Function to check if today is a monitored day is_monitored_day() { - local day_of_week=$(date +%u) # 1=Monday, 7=Sunday + local day_of_week + day_of_week=$(date +%u) # 1=Monday, 7=Sunday # Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7) if [[ "$day_of_week" == "1" ]] || [[ "$day_of_week" == "5" ]] || [[ "$day_of_week" == "6" ]] || [[ "$day_of_week" == "7" ]]; then @@ -266,8 +276,9 @@ is_monitored_day() { # Function to check if current time is between 5AM and 8AM is_current_time_in_window() { - local current_hour=$(date +%H) - local current_hour_num=$((10#$current_hour)) + local current_hour current_hour_num + current_hour=$(date +%H) + current_hour_num=$((10#$current_hour)) if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then return 0 # Yes, current time is in the 5AM-8AM window @@ -278,21 +289,25 @@ is_current_time_in_window() { # Function to check if PC was booted between 5AM-8AM today was_booted_in_window_today() { - local today=$(date +%Y-%m-%d) + local today boot_time + today=$(date +%Y-%m-%d) - # Calculate boot time from uptime - local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") - local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") + # Calculate boot time from uptime + local uptime_seconds + uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") + boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") - # Check if boot time is from today - local boot_date=$(echo "$boot_time" | cut -d' ' -f1) + # Check if boot time is from today + local boot_date + boot_date=$(echo "$boot_time" | cut -d' ' -f1) if [[ "$boot_date" != "$today" ]]; then return 1 # Not booted today fi # Extract hour from boot time - local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) - local boot_hour_num=$((10#$boot_hour)) + local boot_hour boot_hour_num + boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) + boot_hour_num=$((10#$boot_hour)) # Check if boot time was between 5AM and 8AM if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then @@ -304,9 +319,10 @@ was_booted_in_window_today() { # Function to show notification/warning show_startup_warning() { - local day_name=$(date +%A) - local current_time=$(date +"%H:%M") - local today=$(date +%Y-%m-%d) + local day_name current_time today + day_name=$(date +%A) + current_time=$(date +"%H:%M") + today=$(date +%Y-%m-%d) echo "⚠️ PC STARTUP TIME WARNING" echo "Date: $today ($day_name)" @@ -346,19 +362,19 @@ else fi EOF - chmod +x "$script_file" - echo "✓ Created monitoring script: $script_file" + chmod +x "$script_file" + echo "✓ Created monitoring script: $script_file" } # Function to create management script create_management_script() { - echo "" - echo "4. Creating Management Script..." - echo "==============================" - - local script_file="/usr/local/bin/pc-startup-monitor-manager.sh" - - cat > "$script_file" << 'EOF' + echo "" + echo "4. Creating Management Script..." + echo "==============================" + + local script_file="/usr/local/bin/pc-startup-monitor-manager.sh" + + cat > "$script_file" << 'EOF' #!/bin/bash # PC Startup Monitor Manager # Provides easy management of the PC startup monitoring feature @@ -421,150 +437,150 @@ case "$1" in esac EOF - chmod +x "$script_file" - echo "✓ Created management script: $script_file" + chmod +x "$script_file" + echo "✓ Created management script: $script_file" } # Function to enable the services enable_services() { - echo "" - echo "5. Enabling PC Startup Monitor..." - echo "===============================" - - # Reload systemd daemon - systemctl daemon-reload - echo "✓ Reloaded systemd daemon" - - # Enable and start the timer - systemctl enable pc-startup-monitor.timer - echo "✓ Enabled pc-startup-monitor timer" - - systemctl start pc-startup-monitor.timer - echo "✓ Started pc-startup-monitor timer" + echo "" + echo "5. Enabling PC Startup Monitor..." + echo "===============================" + + # Reload systemd daemon + systemctl daemon-reload + echo "✓ Reloaded systemd daemon" + + # Enable and start the timer + systemctl enable pc-startup-monitor.timer + echo "✓ Enabled pc-startup-monitor timer" + + systemctl start pc-startup-monitor.timer + echo "✓ Started pc-startup-monitor timer" } # Function to test the setup test_setup() { - echo "" - echo "6. Testing Setup..." - echo "==================" - - echo "Service files:" - if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then - echo "✓ Service file exists" - else - echo "✗ Service file missing" - fi - - if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then - echo "✓ Timer file exists" - else - echo "✗ Timer file missing" - fi - - echo "" - echo "Timer status:" - if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then - echo "✓ Timer is enabled" - else - echo "✗ Timer is not enabled" - fi - - if systemctl is-active pc-startup-monitor.timer &>/dev/null; then - echo "✓ Timer is active" - else - echo "✗ Timer is not active" - fi - - echo "" - echo "Testing current logic:" - /usr/local/bin/pc-startup-check.sh + echo "" + echo "6. Testing Setup..." + echo "==================" + + echo "Service files:" + if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then + echo "✓ Service file exists" + else + echo "✗ Service file missing" + fi + + if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then + echo "✓ Timer file exists" + else + echo "✗ Timer file missing" + fi + + echo "" + echo "Timer status:" + if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then + echo "✓ Timer is enabled" + else + echo "✗ Timer is not enabled" + fi + + if systemctl is-active pc-startup-monitor.timer &> /dev/null; then + echo "✓ Timer is active" + else + echo "✗ Timer is not active" + fi + + echo "" + echo "Testing current logic:" + /usr/local/bin/pc-startup-check.sh } # Function to show final instructions show_instructions() { - echo "" - echo "==========================================" - echo "PC Startup Monitor Setup Complete" - echo "==========================================" - echo "Summary:" - echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)" - echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)" - echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)" - echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)" - echo "✓ Timer enabled and started" - echo "" - echo "How it works:" - echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday" - echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM" - echo "• Checks daily at 8:30 AM if PC was turned on in expected window" - echo "• Shows warning if PC was not turned on during expected time" - echo "" - echo "Management commands:" - echo " sudo pc-startup-monitor-manager.sh status - Check status" - echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs" - echo " sudo pc-startup-monitor-manager.sh test - Test monitor now" - echo "" - echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)" - echo "" + echo "" + echo "==========================================" + echo "PC Startup Monitor Setup Complete" + echo "==========================================" + echo "Summary:" + echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)" + echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)" + echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)" + echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)" + echo "✓ Timer enabled and started" + echo "" + echo "How it works:" + echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday" + echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM" + echo "• Checks daily at 8:30 AM if PC was turned on in expected window" + echo "• Shows warning if PC was not turned on during expected time" + echo "" + echo "Management commands:" + echo " sudo pc-startup-monitor-manager.sh status - Check status" + echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs" + echo " sudo pc-startup-monitor-manager.sh test - Test monitor now" + echo "" + echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)" + echo "" } # Function to prompt for confirmation confirm_setup() { - echo "" - echo "PC Startup Monitor Setup" - echo "=======================" - echo "This will set up monitoring for PC startup times." - echo "" - echo "Monitoring schedule:" - echo "- Days: Monday, Friday, Saturday, Sunday" - echo "- Expected startup time: 5:00 AM - 8:00 AM" - echo "- Check time: 8:30 AM daily" - echo "- Action: Show warning if PC wasn't started in expected window" - echo "" - - if [[ "$INTERACTIVE_MODE" == "true" ]]; then - read -p "Do you want to proceed? (y/N): " confirm - - case "$confirm" in - [yY]|[yY][eE][sS]) - echo "Proceeding with setup..." - return 0 - ;; - *) - echo "Setup cancelled." - exit 0 - ;; - esac - else - echo "Auto-proceeding with setup (use --interactive to prompt)" + echo "" + echo "PC Startup Monitor Setup" + echo "=======================" + echo "This will set up monitoring for PC startup times." + echo "" + echo "Monitoring schedule:" + echo "- Days: Monday, Friday, Saturday, Sunday" + echo "- Expected startup time: 5:00 AM - 8:00 AM" + echo "- Check time: 8:30 AM daily" + echo "- Action: Show warning if PC wasn't started in expected window" + echo "" + + if [[ $INTERACTIVE_MODE == "true" ]]; then + read -r -p "Do you want to proceed? (y/N): " confirm + + case "$confirm" in + [yY] | [yY][eE][sS]) echo "Proceeding with setup..." return 0 - fi + ;; + *) + echo "Setup cancelled." + exit 0 + ;; + esac + else + echo "Auto-proceeding with setup (use --interactive to prompt)" + echo "Proceeding with setup..." + return 0 + fi } # Main execution flow main() { - # Check for sudo privileges - check_sudo "$@" - - # Confirm setup - confirm_setup - - # Create all components - create_monitoring_service - create_monitoring_timer - create_monitoring_script - create_management_script - - # Enable services - enable_services - - # Test setup - test_setup - - # Show instructions - show_instructions + # Check for sudo privileges + check_sudo "$@" + + # Confirm setup + confirm_setup + + # Create all components + create_monitoring_service + create_monitoring_timer + create_monitoring_script + create_management_script + + # Enable services + enable_services + + # Test setup + test_setup + + # Show instructions + show_instructions } # Run main function diff --git a/scripts/features/control_from_mobile.sh b/scripts/features/control_from_mobile.sh index b5c9129..5e981f9 100755 --- a/scripts/features/control_from_mobile.sh +++ b/scripts/features/control_from_mobile.sh @@ -18,7 +18,7 @@ DEFAULT_BIND_ADDR="0.0.0.0" readonly SCRIPT_NAME CONFIG_DIR STATE_DIR PASSWORD_FILE ENV_FILE RUNNER_FILE SERVICE_NAME SYSTEMD_USER_DIR SERVICE_FILE DEFAULT_DISPLAY DEFAULT_PORT DEFAULT_BIND_ADDR usage() { - cat <<'EOF' + cat << 'EOF' Usage: control_from_mobile.sh [options] Commands: @@ -46,118 +46,117 @@ EOF } log() { - printf '[%s] %s\n' "$SCRIPT_NAME" "$*" + printf '[%s] %s\n' "$SCRIPT_NAME" "$*" } warn() { - printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2 + printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2 } die() { - warn "$*" - exit 1 + warn "$*" + exit 1 } require_non_root() { - if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then - die "Run this script as a regular desktop user, not root." - fi + if [[ ${EUID:-$(id -u)} -eq 0 ]]; then + die "Run this script as a regular desktop user, not root." + fi } prompt_yes_no() { - local prompt="$1" - local reply - read -r -p "$prompt [y/N]: " reply - case "$reply" in - [Yy][Ee][Ss]|[Yy]) return 0 ;; - *) return 1 ;; - esac + local prompt="$1" + local reply + read -r -p "$prompt [y/N]: " reply + case "$reply" in + [Yy][Ee][Ss] | [Yy]) return 0 ;; + *) return 1 ;; + esac } ensure_directories() { - mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR" - chmod 700 "$CONFIG_DIR" + mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR" + chmod 700 "$CONFIG_DIR" } missing_commands() { - local missing=() - for cmd in "$@"; do - if ! command -v "$cmd" >/dev/null 2>&1; then - missing+=("$cmd") - fi - done - printf '%s\n' "${missing[@]-}" + local missing=() + for cmd in "$@"; do + if ! command -v "$cmd" > /dev/null 2>&1; then + missing+=("$cmd") + fi + done + printf '%s\n' "${missing[@]-}" } install_dependencies() { - if ! command -v systemctl >/dev/null 2>&1; then - die "systemctl not found. Install systemd before running this script." - fi + if ! command -v systemctl > /dev/null 2>&1; then + die "systemctl not found. Install systemd before running this script." + fi - local required=(x11vnc qrencode ssh) - local needed=() - mapfile -t needed < <(missing_commands "${required[@]}") - if (( ${#needed[@]} == 0 )); then - log "All required packages (${required[*]}) are present." - return - fi + local required=(x11vnc qrencode ssh) + local needed=() + mapfile -t needed < <(missing_commands "${required[@]}") + if ((${#needed[@]} == 0)); then + log "All required packages (${required[*]}) are present." + return + fi - if command -v pacman >/dev/null 2>&1; then - log "Installing missing packages: ${needed[*]}" - sudo pacman -S --needed --noconfirm "${needed[@]}" - else - die "Missing commands (${needed[*]}). Install them manually and rerun setup." - fi + if command -v pacman > /dev/null 2>&1; then + log "Installing missing packages: ${needed[*]}" + sudo pacman -S --needed --noconfirm "${needed[@]}" + else + die "Missing commands (${needed[*]}). Install them manually and rerun setup." + fi } create_password_file() { - local force=${1:-0} - if [[ -f "$PASSWORD_FILE" && "$force" -ne 1 ]]; - then - log "Using existing VNC password file at $PASSWORD_FILE" - return - fi + local force=${1:-0} + if [[ -f $PASSWORD_FILE && $force -ne 1 ]]; then + log "Using existing VNC password file at $PASSWORD_FILE" + return + fi - if [[ -f "$PASSWORD_FILE" ]]; then - if ! prompt_yes_no "Regenerate the stored VNC password?"; then - log "Keeping existing password." - return - fi - fi + if [[ -f $PASSWORD_FILE ]]; then + if ! prompt_yes_no "Regenerate the stored VNC password?"; then + log "Keeping existing password." + return + fi + fi - local password confirm generated=0 - read -rsp "Enter VNC password (leave blank to auto-generate): " password - printf '\n' - if [[ -z "$password" ]]; then - generated=1 - password=$(LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null - install -m 600 "$tmp" "$PASSWORD_FILE" - rm -f "$tmp" + local tmp + tmp=$(mktemp) + x11vnc -storepasswd "$password" "$tmp" > /dev/null + install -m 600 "$tmp" "$PASSWORD_FILE" + rm -f "$tmp" - if (( generated == 0 )); then - log "Password stored securely at $PASSWORD_FILE (hashed)." - else - log "Please write down the generated password; it will be needed on your Android device." - fi + if ((generated == 0)); then + log "Password stored securely at $PASSWORD_FILE (hashed)." + else + log "Please write down the generated password; it will be needed on your Android device." + fi } create_env_file() { - if [[ -f "$ENV_FILE" ]]; then - return - fi - cat >"$ENV_FILE" < "$ENV_FILE" << EOF # control-from-mobile configuration # Adjust these values if needed and rerun: systemctl --user restart $SERVICE_NAME X11_DISPLAY="$DEFAULT_DISPLAY" @@ -165,11 +164,11 @@ VNC_PORT="$DEFAULT_PORT" # Use 127.0.0.1 to force SSH tunnel-only access, or 0.0.0.0 to expose on LAN. VNC_BIND_ADDR="$DEFAULT_BIND_ADDR" EOF - chmod 600 "$ENV_FILE" + chmod 600 "$ENV_FILE" } create_runner_script() { - cat >"$RUNNER_FILE" <<'EOF' + cat > "$RUNNER_FILE" << 'EOF' #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' @@ -209,11 +208,11 @@ exec /usr/bin/x11vnc \ -ncache_cr \ -o "$LOG_FILE" EOF - chmod 700 "$RUNNER_FILE" + chmod 700 "$RUNNER_FILE" } create_service_file() { - cat >"$SERVICE_FILE" < "$SERVICE_FILE" << EOF [Unit] Description=Expose X11 desktop over VNC for Android control After=graphical-session.target @@ -234,183 +233,183 @@ EOF } reload_user_daemon() { - systemctl --user daemon-reload + systemctl --user daemon-reload } ensure_service_present() { - if [[ ! -f "$SERVICE_FILE" || ! -x "$RUNNER_FILE" ]]; then - die "Service files missing. Run: $SCRIPT_NAME setup" - fi + if [[ ! -f $SERVICE_FILE || ! -x $RUNNER_FILE ]]; then + die "Service files missing. Run: $SCRIPT_NAME setup" + fi } start_service() { - ensure_service_present - systemctl --user start "$SERVICE_NAME" + ensure_service_present + systemctl --user start "$SERVICE_NAME" } stop_service() { - systemctl --user stop "$SERVICE_NAME" || true + systemctl --user stop "$SERVICE_NAME" || true } status_service() { - if systemctl --user is-active --quiet "$SERVICE_NAME"; then - log "Service is active." - else - log "Service is inactive." - fi - systemctl --user status "$SERVICE_NAME" --no-pager || true + if systemctl --user is-active --quiet "$SERVICE_NAME"; then + log "Service is active." + else + log "Service is inactive." + fi + systemctl --user status "$SERVICE_NAME" --no-pager || true } enable_service() { - ensure_service_present - systemctl --user enable "$SERVICE_NAME" + ensure_service_present + systemctl --user enable "$SERVICE_NAME" } disable_service() { - systemctl --user disable "$SERVICE_NAME" || true + systemctl --user disable "$SERVICE_NAME" || true } show_info() { - ensure_service_present - # shellcheck disable=SC1090 - [[ -f "$ENV_FILE" ]] && source "$ENV_FILE" - local port="${VNC_PORT:-$DEFAULT_PORT}" - local bind_addr="${VNC_BIND_ADDR:-$DEFAULT_BIND_ADDR}" - local display="${X11_DISPLAY:-$DEFAULT_DISPLAY}" + ensure_service_present + # shellcheck disable=SC1090 + [[ -f $ENV_FILE ]] && source "$ENV_FILE" + local port="${VNC_PORT:-$DEFAULT_PORT}" + local bind_addr="${VNC_BIND_ADDR:-$DEFAULT_BIND_ADDR}" + local display="${X11_DISPLAY:-$DEFAULT_DISPLAY}" - local is_active="inactive" - if systemctl --user is-active --quiet "$SERVICE_NAME"; then - is_active="active" - fi + local is_active="inactive" + if systemctl --user is-active --quiet "$SERVICE_NAME"; then + is_active="active" + fi - log "Service status: $is_active" - log "Display: $display" - log "Listening address: $bind_addr" - log "VNC port: $port" - log "Password file: $PASSWORD_FILE" + log "Service status: $is_active" + log "Display: $display" + log "Listening address: $bind_addr" + log "VNC port: $port" + log "Password file: $PASSWORD_FILE" - local -a ip_list=() - if command -v hostname >/dev/null 2>&1; then - while IFS= read -r line; do - [[ -z "$line" ]] && continue - ip_list+=("$line") - done < <(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true) - fi + local -a ip_list=() + if command -v hostname > /dev/null 2>&1; then + while IFS= read -r line; do + [[ -z $line ]] && continue + ip_list+=("$line") + done < <(hostname -I 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true) + fi - if (( ${#ip_list[@]} > 0 )); then - log "Detected LAN IPs:" - for ip in "${ip_list[@]}"; do - printf ' - %s\n' "$ip" - done - else - warn "Could not detect LAN IPs." - fi + if ((${#ip_list[@]} > 0)); then + log "Detected LAN IPs:" + for ip in "${ip_list[@]}"; do + printf ' - %s\n' "$ip" + done + else + warn "Could not detect LAN IPs." + fi - printf '\nRecommended Android clients (FOSS):\n' - printf ' • bVNC (available on F-Droid) — supports full control.\n' - printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n' - printf '\nConnect via VNC:\n' - printf ' Host: \n Port: %s\n Password: \n' "$port" + printf '\nRecommended Android clients (FOSS):\n' + printf ' • bVNC (available on F-Droid) — supports full control.\n' + printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n' + printf '\nConnect via VNC:\n' + printf ' Host: \n Port: %s\n Password: \n' "$port" - local qr_host - if (( ${#ip_list[@]} > 0 )); then - qr_host="${ip_list[0]}" - else - qr_host="$bind_addr" - if [[ "$qr_host" == "0.0.0.0" || "$qr_host" == "::" ]]; then - qr_host="127.0.0.1" - fi - warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed." - fi + local qr_host + if ((${#ip_list[@]} > 0)); then + qr_host="${ip_list[0]}" + else + qr_host="$bind_addr" + if [[ $qr_host == "0.0.0.0" || $qr_host == "::" ]]; then + qr_host="127.0.0.1" + fi + warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed." + fi - if command -v qrencode >/dev/null 2>&1; then - printf '\nConnection QR (vnc://%s:%s):\n' "$qr_host" "$port" - qrencode -o - "vnc://$qr_host:$port" -t ASCII || true - else - warn "qrencode not found; reinstall qrencode to get QR codes." - fi + if command -v qrencode > /dev/null 2>&1; then + printf '\nConnection QR (vnc://%s:%s):\n' "$qr_host" "$port" + qrencode -o - "vnc://$qr_host:$port" -t ASCII || true + else + warn "qrencode not found; reinstall qrencode to get QR codes." + fi - printf '\nFor encrypted access outside your LAN, use Termux on Android:\n' - printf ' ssh -L %s:localhost:%s @\n' "$port" "$port" - printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port" + printf '\nFor encrypted access outside your LAN, use Termux on Android:\n' + printf ' ssh -L %s:localhost:%s @\n' "$port" "$port" + printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port" } uninstall_files() { - local purge_password=${1:-0} - stop_service - disable_service - rm -f "$SERVICE_FILE" - rm -f "$RUNNER_FILE" - rm -f "$ENV_FILE" - if (( purge_password )); then - rm -f "$PASSWORD_FILE" - log "Removed password file." - fi - reload_user_daemon - log "Removed generated files." + local purge_password=${1:-0} + stop_service + disable_service + rm -f "$SERVICE_FILE" + rm -f "$RUNNER_FILE" + rm -f "$ENV_FILE" + if ((purge_password)); then + rm -f "$PASSWORD_FILE" + log "Removed password file." + fi + reload_user_daemon + log "Removed generated files." } main() { - require_non_root + require_non_root - local cmd="${1:-}" - shift || true + local cmd="${1:-}" + shift || true - case "$cmd" in - setup) - local force=0 - if [[ "${1:-}" == "--force-password" ]]; then - force=1 - shift || true - fi - ensure_directories - install_dependencies - create_password_file "$force" - create_env_file - create_runner_script - create_service_file - reload_user_daemon - log "Setup complete. Start the service with: $SCRIPT_NAME start" - ;; - start) - start_service - show_info - ;; - stop) - stop_service - ;; - restart) - stop_service - start_service - ;; - status) - status_service - ;; - enable) - enable_service - ;; - disable) - disable_service - ;; - info) - show_info - ;; - uninstall) - local purge=0 - if [[ "${1:-}" == "--purge" ]]; then - purge=1 - shift || true - fi - uninstall_files "$purge" - ;; - help|--help|-h|"" ) - usage - ;; - *) - usage - die "Unknown command: $cmd" - ;; - esac + case "$cmd" in + setup) + local force=0 + if [[ ${1:-} == "--force-password" ]]; then + force=1 + shift || true + fi + ensure_directories + install_dependencies + create_password_file "$force" + create_env_file + create_runner_script + create_service_file + reload_user_daemon + log "Setup complete. Start the service with: $SCRIPT_NAME start" + ;; + start) + start_service + show_info + ;; + stop) + stop_service + ;; + restart) + stop_service + start_service + ;; + status) + status_service + ;; + enable) + enable_service + ;; + disable) + disable_service + ;; + info) + show_info + ;; + uninstall) + local purge=0 + if [[ ${1:-} == "--purge" ]]; then + purge=1 + shift || true + fi + uninstall_files "$purge" + ;; + help | --help | -h | "") + usage + ;; + *) + usage + die "Unknown command: $cmd" + ;; + esac } main "$@" diff --git a/scripts/features/setup_activitywatch.sh b/scripts/features/setup_activitywatch.sh index 0fb9506..b205075 100755 --- a/scripts/features/setup_activitywatch.sh +++ b/scripts/features/setup_activitywatch.sh @@ -3,15 +3,15 @@ # Handles installation, startup, autostart, and i3blocks status # Handles sudo privileges automatically -set -e # Exit on any error +set -e # Exit on any error # Function to check and request sudo privileges for package installation check_sudo() { - if [[ $EUID -ne 0 ]] && [[ "$1" == "install" ]]; then - echo "Package installation requires sudo privileges." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then + echo "Package installation requires sudo privileges." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } echo "ActivityWatch Setup for Arch Linux + i3" @@ -20,12 +20,12 @@ echo "Current Date: $(date)" echo "User: ${SUDO_USER:-$USER}" # Get the actual user (even when running with sudo) -if [[ -n "$SUDO_USER" ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" +if [[ -n $SUDO_USER ]]; then + ACTUAL_USER="$SUDO_USER" + USER_HOME="/home/$SUDO_USER" else - ACTUAL_USER="$USER" - USER_HOME="$HOME" + ACTUAL_USER="$USER" + USER_HOME="$HOME" fi echo "Target user: $ACTUAL_USER" @@ -33,180 +33,180 @@ echo "User home: $USER_HOME" # Function to check if ActivityWatch is installed check_activitywatch_installed() { - echo "" - echo "1. Checking ActivityWatch Installation..." - echo "========================================" - - # Check if activitywatch-bin is installed via pacman - if pacman -Qi activitywatch-bin &>/dev/null; then - echo "✓ activitywatch-bin package is installed" - return 0 + echo "" + echo "1. Checking ActivityWatch Installation..." + echo "========================================" + + # Check if activitywatch-bin is installed via pacman + if pacman -Qi activitywatch-bin &> /dev/null; then + echo "✓ activitywatch-bin package is installed" + return 0 + fi + + # Check if aw-qt binary exists in common locations + local common_paths=( + "/usr/bin/aw-qt" + "/usr/local/bin/aw-qt" + "$USER_HOME/.local/bin/aw-qt" + "$USER_HOME/activitywatch/aw-qt" + ) + + for path in "${common_paths[@]}"; do + if [[ -x $path ]]; then + echo "✓ ActivityWatch found at: $path" + return 0 fi - - # Check if aw-qt binary exists in common locations - local common_paths=( - "/usr/bin/aw-qt" - "/usr/local/bin/aw-qt" - "$USER_HOME/.local/bin/aw-qt" - "$USER_HOME/activitywatch/aw-qt" - ) - - for path in "${common_paths[@]}"; do - if [[ -x "$path" ]]; then - echo "✓ ActivityWatch found at: $path" - return 0 - fi - done - - echo "✗ ActivityWatch not found" - return 1 + done + + echo "✗ ActivityWatch not found" + return 1 } # Function to install ActivityWatch install_activitywatch() { - echo "" - echo "2. Installing ActivityWatch..." - echo "=============================" - - # Check if we need sudo for installation - check_sudo "install" - - echo "Installing activitywatch-bin from AUR..." - - # Check if an AUR helper is available - local aur_helpers=("yay" "paru" "makepkg") - local helper_found="" - - for helper in "${aur_helpers[@]}"; do - if command -v "$helper" &>/dev/null; then - helper_found="$helper" - break - fi - done - - if [[ -n "$helper_found" && "$helper_found" != "makepkg" ]]; then - echo "Using AUR helper: $helper_found" - if [[ $EUID -eq 0 ]]; then - # Running as root, need to install as user - sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin - else - "$helper_found" -S --noconfirm activitywatch-bin - fi - else - echo "No AUR helper found. Installing manually with makepkg..." - install_activitywatch_manual + echo "" + echo "2. Installing ActivityWatch..." + echo "=============================" + + # Check if we need sudo for installation + check_sudo "install" + + echo "Installing activitywatch-bin from AUR..." + + # Check if an AUR helper is available + local aur_helpers=("yay" "paru" "makepkg") + local helper_found="" + + for helper in "${aur_helpers[@]}"; do + if command -v "$helper" &> /dev/null; then + helper_found="$helper" + break fi - - echo "✓ ActivityWatch installation completed" + done + + if [[ -n $helper_found && $helper_found != "makepkg" ]]; then + echo "Using AUR helper: $helper_found" + if [[ $EUID -eq 0 ]]; then + # Running as root, need to install as user + sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin + else + "$helper_found" -S --noconfirm activitywatch-bin + fi + else + echo "No AUR helper found. Installing manually with makepkg..." + install_activitywatch_manual + fi + + echo "✓ ActivityWatch installation completed" } # Function to manually install ActivityWatch via makepkg install_activitywatch_manual() { - local temp_dir="/tmp/activitywatch-install" - local original_user="$ACTUAL_USER" - - # Create temp directory - mkdir -p "$temp_dir" - cd "$temp_dir" - - # Download PKGBUILD - if command -v git &>/dev/null; then - sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . - else - echo "Installing git..." - pacman -S --noconfirm git - sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . - fi - - # Build and install package - sudo -u "$original_user" makepkg -si --noconfirm - - # Cleanup - cd / - rm -rf "$temp_dir" + local temp_dir="/tmp/activitywatch-install" + local original_user="$ACTUAL_USER" + + # Create temp directory + mkdir -p "$temp_dir" + cd "$temp_dir" + + # Download PKGBUILD + if command -v git &> /dev/null; then + sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . + else + echo "Installing git..." + pacman -S --noconfirm git + sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . + fi + + # Build and install package + sudo -u "$original_user" makepkg -si --noconfirm + + # Cleanup + cd / + rm -rf "$temp_dir" } # Function to check if ActivityWatch is running check_activitywatch_running() { - echo "" - echo "3. Checking ActivityWatch Status..." - echo "==================================" - - # Check for aw-qt process - if pgrep -f "aw-qt" >/dev/null; then - echo "✓ ActivityWatch (aw-qt) is running" - return 0 - fi - - # Check for aw-server process - if pgrep -f "aw-server" >/dev/null; then - echo "✓ ActivityWatch server is running" - return 0 - fi - - echo "✗ ActivityWatch is not running" - return 1 + echo "" + echo "3. Checking ActivityWatch Status..." + echo "==================================" + + # Check for aw-qt process + if pgrep -f "aw-qt" > /dev/null; then + echo "✓ ActivityWatch (aw-qt) is running" + return 0 + fi + + # Check for aw-server process + if pgrep -f "aw-server" > /dev/null; then + echo "✓ ActivityWatch server is running" + return 0 + fi + + echo "✗ ActivityWatch is not running" + return 1 } # Function to start ActivityWatch start_activitywatch() { - echo "" - echo "4. Starting ActivityWatch..." - echo "===========================" - - # Find aw-qt executable - local aw_qt_path="" - - if command -v aw-qt &>/dev/null; then - aw_qt_path="$(which aw-qt)" - elif [[ -x "/usr/bin/aw-qt" ]]; then - aw_qt_path="/usr/bin/aw-qt" - else - echo "✗ Could not find aw-qt executable" - return 1 - fi - - echo "Starting ActivityWatch as user: $ACTUAL_USER" - echo "Using aw-qt from: $aw_qt_path" - - # Start as the actual user in the background - if [[ $EUID -eq 0 ]]; then - # Running as root, start as user - sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" & - else - # Running as user - "$aw_qt_path" & - fi - - # Give it time to start - sleep 3 - - if check_activitywatch_running >/dev/null 2>&1; then - echo "✓ ActivityWatch started successfully" - else - echo "! ActivityWatch may be starting (check system tray)" - fi + echo "" + echo "4. Starting ActivityWatch..." + echo "===========================" + + # Find aw-qt executable + local aw_qt_path="" + + if command -v aw-qt &> /dev/null; then + aw_qt_path="$(which aw-qt)" + elif [[ -x "/usr/bin/aw-qt" ]]; then + aw_qt_path="/usr/bin/aw-qt" + else + echo "✗ Could not find aw-qt executable" + return 1 + fi + + echo "Starting ActivityWatch as user: $ACTUAL_USER" + echo "Using aw-qt from: $aw_qt_path" + + # Start as the actual user in the background + if [[ $EUID -eq 0 ]]; then + # Running as root, start as user + sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" & + else + # Running as user + "$aw_qt_path" & + fi + + # Give it time to start + sleep 3 + + if check_activitywatch_running > /dev/null 2>&1; then + echo "✓ ActivityWatch started successfully" + else + echo "! ActivityWatch may be starting (check system tray)" + fi } # Function to setup autostart setup_autostart() { - echo "" - echo "5. Setting Up Autostart..." - echo "=========================" - - local autostart_dir="$USER_HOME/.config/autostart" - local desktop_file="$autostart_dir/activitywatch.desktop" - local i3_config="$USER_HOME/.config/i3/config" - - # Method 1: XDG Autostart (works with most desktop environments) - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir" - else - mkdir -p "$autostart_dir" - fi - - # Create desktop file for autostart - cat > "$desktop_file" << EOF + echo "" + echo "5. Setting Up Autostart..." + echo "=========================" + + local autostart_dir="$USER_HOME/.config/autostart" + local desktop_file="$autostart_dir/activitywatch.desktop" + local i3_config="$USER_HOME/.config/i3/config" + + # Method 1: XDG Autostart (works with most desktop environments) + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir" + else + mkdir -p "$autostart_dir" + fi + + # Create desktop file for autostart + cat > "$desktop_file" << EOF [Desktop Entry] Type=Application Name=ActivityWatch @@ -220,59 +220,61 @@ StartupNotify=false Terminal=false Categories=Utility; EOF - - # Set proper ownership if running as root - if [[ $EUID -eq 0 ]]; then - chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file" - fi - - echo "✓ Created XDG autostart entry: $desktop_file" - - # Method 2: i3 config autostart (specific to i3) - if [[ -f "$i3_config" ]]; then - # Check if autostart entry already exists - if ! grep -q "aw-qt" "$i3_config"; then - # Add autostart entry to i3 config - local temp_config="/tmp/i3_config_temp" - - if [[ $EUID -eq 0 ]]; then - # Running as root - sudo -u "$ACTUAL_USER" bash -c "echo '' >> '$i3_config'" - sudo -u "$ACTUAL_USER" bash -c "echo '# Auto-start ActivityWatch' >> '$i3_config'" - sudo -u "$ACTUAL_USER" bash -c "echo 'exec --no-startup-id aw-qt' >> '$i3_config'" - else - echo "" >> "$i3_config" - echo "# Auto-start ActivityWatch" >> "$i3_config" - echo "exec --no-startup-id aw-qt" >> "$i3_config" - fi - - echo "✓ Added ActivityWatch to i3 config autostart" - else - echo "✓ ActivityWatch autostart already exists in i3 config" - fi + + # Set proper ownership if running as root + if [[ $EUID -eq 0 ]]; then + chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file" + fi + + echo "✓ Created XDG autostart entry: $desktop_file" + + # Method 2: i3 config autostart (specific to i3) + if [[ -f $i3_config ]]; then + # Check if autostart entry already exists + if ! grep -q "aw-qt" "$i3_config"; then + # Add autostart entry to i3 config + if [[ $EUID -eq 0 ]]; then + # Running as root + sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config' + +# Auto-start ActivityWatch +exec --no-startup-id aw-qt +EOF" + else + { + printf '\n' + printf '# Auto-start ActivityWatch\n' + printf 'exec --no-startup-id aw-qt\n' + } >> "$i3_config" + fi + + echo "✓ Added ActivityWatch to i3 config autostart" else - echo "! i3 config not found at $i3_config" + echo "✓ ActivityWatch autostart already exists in i3 config" fi + else + echo "! i3 config not found at $i3_config" + fi } # Function to create i3blocks status script create_i3blocks_status() { - echo "" - echo "6. Creating i3blocks Status Script..." - echo "====================================" - - local i3blocks_dir="$USER_HOME/.config/i3blocks" - local status_script="$i3blocks_dir/activitywatch_status.sh" - - # Create i3blocks directory if it doesn't exist - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir" - else - mkdir -p "$i3blocks_dir" - fi - - # Create the status script - cat > "$status_script" << 'EOF' + echo "" + echo "6. Creating i3blocks Status Script..." + echo "====================================" + + local i3blocks_dir="$USER_HOME/.config/i3blocks" + local status_script="$i3blocks_dir/activitywatch_status.sh" + + # Create i3blocks directory if it doesn't exist + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir" + else + mkdir -p "$i3blocks_dir" + fi + + # Create the status script + cat > "$status_script" << 'EOF' #!/bin/bash # ActivityWatch status script for i3blocks # Shows ActivityWatch installation and running status @@ -323,134 +325,134 @@ else fi EOF - chmod +x "$status_script" - - # Set proper ownership if running as root - if [[ $EUID -eq 0 ]]; then - chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script" - fi - - echo "✓ Created i3blocks status script: $status_script" - - # Show configuration instructions - echo "" - echo "To add to your i3blocks config, add this block:" - echo "" - echo "[activitywatch]" - echo "command=~/.config/i3blocks/activitywatch_status.sh" - echo "interval=10" - echo "color=#FFFFFF" - echo "" + chmod +x "$status_script" + + # Set proper ownership if running as root + if [[ $EUID -eq 0 ]]; then + chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script" + fi + + echo "✓ Created i3blocks status script: $status_script" + + # Show configuration instructions + echo "" + echo "To add to your i3blocks config, add this block:" + echo "" + echo "[activitywatch]" + echo "command=~/.config/i3blocks/activitywatch_status.sh" + echo "interval=10" + echo "color=#FFFFFF" + echo "" } # Function to test the setup test_setup() { - echo "" - echo "7. Testing Setup..." - echo "==================" - - echo "Installation status:" - if check_activitywatch_installed >/dev/null 2>&1; then - echo "✓ ActivityWatch is installed" + echo "" + echo "7. Testing Setup..." + echo "==================" + + echo "Installation status:" + if check_activitywatch_installed > /dev/null 2>&1; then + echo "✓ ActivityWatch is installed" + else + echo "✗ ActivityWatch is not installed" + fi + + echo "Running status:" + if check_activitywatch_running > /dev/null 2>&1; then + echo "✓ ActivityWatch is running" + else + echo "✗ ActivityWatch is not running" + fi + + echo "Autostart files:" + if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then + echo "✓ XDG autostart file exists" + else + echo "✗ XDG autostart file missing" + fi + + if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then + echo "✓ i3 autostart configured" + else + echo "! i3 autostart may not be configured" + fi + + echo "i3blocks status script:" + if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then + echo "✓ i3blocks status script created" + echo "Testing status script:" + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh" else - echo "✗ ActivityWatch is not installed" - fi - - echo "Running status:" - if check_activitywatch_running >/dev/null 2>&1; then - echo "✓ ActivityWatch is running" - else - echo "✗ ActivityWatch is not running" - fi - - echo "Autostart files:" - if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then - echo "✓ XDG autostart file exists" - else - echo "✗ XDG autostart file missing" - fi - - if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then - echo "✓ i3 autostart configured" - else - echo "! i3 autostart may not be configured" - fi - - echo "i3blocks status script:" - if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then - echo "✓ i3blocks status script created" - echo "Testing status script:" - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh" - else - "$USER_HOME/.config/i3blocks/activitywatch_status.sh" - fi - else - echo "✗ i3blocks status script missing" + "$USER_HOME/.config/i3blocks/activitywatch_status.sh" fi + else + echo "✗ i3blocks status script missing" + fi } # Function to show final instructions show_instructions() { - echo "" - echo "==========================================" - echo "ActivityWatch Setup Complete" - echo "==========================================" - echo "Summary:" - echo "✓ ActivityWatch installation checked/completed" - echo "✓ ActivityWatch startup configured" - echo "✓ Autostart configured (XDG + i3)" - echo "✓ i3blocks status script created" - echo "" - echo "Next steps:" - echo "1. Add the i3blocks configuration to your config file:" - echo " ~/.config/i3blocks/config" - echo "" - echo "2. Reload i3 configuration:" - echo " Super+Shift+R" - echo "" - echo "3. ActivityWatch web interface should be available at:" - echo " http://localhost:5600" - echo "" - echo "4. Check system tray for ActivityWatch icon" - echo "" - echo "Files created:" - echo " ~/.config/autostart/activitywatch.desktop" - echo " ~/.config/i3blocks/activitywatch_status.sh" - echo " ~/.config/i3/config (modified)" - echo "" + echo "" + echo "==========================================" + echo "ActivityWatch Setup Complete" + echo "==========================================" + echo "Summary:" + echo "✓ ActivityWatch installation checked/completed" + echo "✓ ActivityWatch startup configured" + echo "✓ Autostart configured (XDG + i3)" + echo "✓ i3blocks status script created" + echo "" + echo "Next steps:" + echo "1. Add the i3blocks configuration to your config file:" + echo " ~/.config/i3blocks/config" + echo "" + echo "2. Reload i3 configuration:" + echo " Super+Shift+R" + echo "" + echo "3. ActivityWatch web interface should be available at:" + echo " http://localhost:5600" + echo "" + echo "4. Check system tray for ActivityWatch icon" + echo "" + echo "Files created:" + echo " ~/.config/autostart/activitywatch.desktop" + echo " ~/.config/i3blocks/activitywatch_status.sh" + echo " ~/.config/i3/config (modified)" + echo "" } # Main execution flow main() { - local need_install=false - local need_start=false - - # Check installation - if ! check_activitywatch_installed; then - need_install=true - fi - - # Install if needed - if [[ "$need_install" == true ]]; then - install_activitywatch - fi - - # Check if running - if ! check_activitywatch_running; then - need_start=true - fi - - # Start if needed - if [[ "$need_start" == true ]]; then - start_activitywatch - fi - - # Always set up autostart and i3blocks (in case they're missing) - setup_autostart - create_i3blocks_status - test_setup - show_instructions + local need_install=false + local need_start=false + + # Check installation + if ! check_activitywatch_installed; then + need_install=true + fi + + # Install if needed + if [[ $need_install == true ]]; then + install_activitywatch + fi + + # Check if running + if ! check_activitywatch_running; then + need_start=true + fi + + # Start if needed + if [[ $need_start == true ]]; then + start_activitywatch + fi + + # Always set up autostart and i3blocks (in case they're missing) + setup_autostart + create_i3blocks_status + test_setup + show_instructions } # Run main function diff --git a/scripts/fixes/fix_virtualbox.sh b/scripts/fixes/fix_virtualbox.sh index 5500ce9..f60efec 100644 --- a/scripts/fixes/fix_virtualbox.sh +++ b/scripts/fixes/fix_virtualbox.sh @@ -3,194 +3,194 @@ set -euo pipefail on_error() { - local exit_code=$? - local line_number=$1 - printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2 + local exit_code=$? + local line_number=$1 + printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2 } trap 'on_error ${LINENO}' ERR log_info() { - printf '\033[1;34m[INFO]\033[0m %s\n' "$*" + printf '\033[1;34m[INFO]\033[0m %s\n' "$*" } log_warn() { - printf '\033[1;33m[WARN]\033[0m %s\n' "$*" + printf '\033[1;33m[WARN]\033[0m %s\n' "$*" } log_error() { - printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 - exit 1 + printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 + exit 1 } require_root() { - if [[ "${EUID}" -ne 0 ]]; then - log_error "This script must be run as root (try again with sudo)." - fi + if [[ ${EUID} -ne 0 ]]; then + log_error "This script must be run as root (try again with sudo)." + fi } require_pacman() { - if ! command -v pacman >/dev/null 2>&1; then - log_error "pacman not found. This script is intended for Arch Linux systems." - fi + if ! command -v pacman > /dev/null 2>&1; then + log_error "pacman not found. This script is intended for Arch Linux systems." + fi } detect_kernel_release() { - uname -r + uname -r } select_host_package() { - local kernel_release=$1 - case "${kernel_release}" in - *-lts) - echo "virtualbox-host-modules-lts" - ;; - *-arch*) - echo "virtualbox-host-modules-arch" - ;; - *) - echo "virtualbox-host-dkms" - ;; - esac + local kernel_release=$1 + case "${kernel_release}" in + *-lts) + echo "virtualbox-host-modules-lts" + ;; + *-arch*) + echo "virtualbox-host-modules-arch" + ;; + *) + echo "virtualbox-host-dkms" + ;; + esac } collect_kernel_headers() { - local -a headers=() - local kernel_pkg header_pkg - for kernel_pkg in linux linux-lts linux-zen linux-hardened; do - if pacman -Q "${kernel_pkg}" >/dev/null 2>&1; then - header_pkg="${kernel_pkg}-headers" - headers+=("${header_pkg}") - fi - done - if [[ ${#headers[@]} -gt 0 ]]; then - printf '%s\n' "${headers[@]}" - fi + local -a headers=() + local kernel_pkg header_pkg + for kernel_pkg in linux linux-lts linux-zen linux-hardened; do + if pacman -Q "${kernel_pkg}" > /dev/null 2>&1; then + header_pkg="${kernel_pkg}-headers" + headers+=("${header_pkg}") + fi + done + if [[ ${#headers[@]} -gt 0 ]]; then + printf '%s\n' "${headers[@]}" + fi } maybe_remove_conflicting_host_packages() { - local selected_package=$1 - local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") - local pkg - for pkg in "${candidates[@]}"; do - if [[ "${pkg}" != "${selected_package}" ]] && pacman -Q "${pkg}" >/dev/null 2>&1; then - log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." - pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" - fi - done + local selected_package=$1 + local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") + local pkg + for pkg in "${candidates[@]}"; do + if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" > /dev/null 2>&1; then + log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." + pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" + fi + done } install_packages() { - local -a packages=() - local -a headers=() - local host_package=$1 - shift - if [[ $# -gt 0 ]]; then - mapfile -t headers < <(printf '%s\n' "$@" | sort -u) - fi - packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") - if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then - packages+=("dkms") - fi - if [[ ${#headers[@]} -gt 0 ]]; then - packages+=("${headers[@]}") - fi - log_info "Installing packages: ${packages[*]}" - pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" + local -a packages=() + local -a headers=() + local host_package=$1 + shift + if [[ $# -gt 0 ]]; then + mapfile -t headers < <(printf '%s\n' "$@" | sort -u) + fi + packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") + if [[ ${host_package} == "virtualbox-host-dkms" ]]; then + packages+=("dkms") + fi + if [[ ${#headers[@]} -gt 0 ]]; then + packages+=("${headers[@]}") + fi + log_info "Installing packages: ${packages[*]}" + pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" } rebuild_virtualbox_modules() { - local host_package=$1 - if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then - if command -v dkms >/dev/null 2>&1; then - log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." - dkms autoinstall - else - log_warn "dkms command not found; skipping DKMS rebuild." - fi - fi + local host_package=$1 + if [[ ${host_package} == "virtualbox-host-dkms" ]]; then + if command -v dkms > /dev/null 2>&1; then + log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." + dkms autoinstall + else + log_warn "dkms command not found; skipping DKMS rebuild." + fi + fi } reload_virtualbox_modules() { - log_info "Loading VirtualBox kernel modules." - if [[ -x /sbin/rcvboxdrv ]]; then - /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." - elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then - /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." - fi + log_info "Loading VirtualBox kernel modules." + if [[ -x /sbin/rcvboxdrv ]]; then + /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." + elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then + /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." + fi - local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) - local mod - for mod in "${modules[@]}"; do - if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then - if ! modprobe "${mod}" >/dev/null 2>&1; then - log_warn "Module ${mod} failed to load; check dmesg for details." - fi - fi - done + local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) + local mod + for mod in "${modules[@]}"; do + if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then + if ! modprobe "${mod}" > /dev/null 2>&1; then + log_warn "Module ${mod} failed to load; check dmesg for details." + fi + fi + done - if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then - log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." - fi - log_info "VirtualBox kernel driver loaded successfully." + if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then + log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." + fi + log_info "VirtualBox kernel driver loaded successfully." } warn_if_secure_boot_enabled() { - local secure_boot_file - if [[ -d /sys/firmware/efi/efivars ]]; then - secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2>/dev/null || true) - if [[ -n "${secure_boot_file}" && -r "${secure_boot_file}" ]]; then - local state - state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2>/dev/null || echo "0") - if [[ "${state}" == "1" ]]; then - log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." - fi - fi - fi + local secure_boot_file + if [[ -d /sys/firmware/efi/efivars ]]; then + secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2> /dev/null || true) + if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then + local state + state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2> /dev/null || echo "0") + if [[ ${state} == "1" ]]; then + log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." + fi + fi + fi } remind_group_membership() { - local invoking_user=${SUDO_USER:-} - if [[ -n "${invoking_user}" && "${invoking_user}" != "root" ]]; then - if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then - log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" - else - log_info "User ${invoking_user} is already in the vboxusers group." - fi - fi + local invoking_user=${SUDO_USER:-} + if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then + if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then + log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" + else + log_info "User ${invoking_user} is already in the vboxusers group." + fi + fi } main() { - require_root - require_pacman + require_root + require_pacman - PACMAN_INSTALL_FLAGS=(--needed) - PACMAN_REMOVE_FLAGS=() - if [[ "${PACMAN_CONFIRM:-0}" == "1" ]]; then - log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." - else - PACMAN_INSTALL_FLAGS+=(--noconfirm) - PACMAN_REMOVE_FLAGS+=(--noconfirm) - fi + PACMAN_INSTALL_FLAGS=(--needed) + PACMAN_REMOVE_FLAGS=() + if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then + log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." + else + PACMAN_INSTALL_FLAGS+=(--noconfirm) + PACMAN_REMOVE_FLAGS+=(--noconfirm) + fi - local kernel_release host_package - kernel_release=$(detect_kernel_release) - log_info "Detected running kernel: ${kernel_release}" - host_package=$(select_host_package "${kernel_release}") - log_info "Selected VirtualBox host package: ${host_package}" + local kernel_release host_package + kernel_release=$(detect_kernel_release) + log_info "Detected running kernel: ${kernel_release}" + host_package=$(select_host_package "${kernel_release}") + log_info "Selected VirtualBox host package: ${host_package}" - mapfile -t kernel_headers < <(collect_kernel_headers) - if [[ "${host_package}" == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then - log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." - fi + mapfile -t kernel_headers < <(collect_kernel_headers) + if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then + log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." + fi - maybe_remove_conflicting_host_packages "${host_package}" - install_packages "${host_package}" "${kernel_headers[@]}" - rebuild_virtualbox_modules "${host_package}" - reload_virtualbox_modules - warn_if_secure_boot_enabled - remind_group_membership + maybe_remove_conflicting_host_packages "${host_package}" + install_packages "${host_package}" "${kernel_headers[@]}" + rebuild_virtualbox_modules "${host_package}" + reload_virtualbox_modules + warn_if_secure_boot_enabled + remind_group_membership - log_info "VirtualBox installation and driver setup complete." + log_info "VirtualBox installation and driver setup complete." } main "$@" diff --git a/scripts/fixes/nvidia_troubleshoot.sh b/scripts/fixes/nvidia_troubleshoot.sh index f11e3f1..666cc41 100755 --- a/scripts/fixes/nvidia_troubleshoot.sh +++ b/scripts/fixes/nvidia_troubleshoot.sh @@ -3,40 +3,40 @@ # Script to disable NVIDIA GSP firmware and apply comprehensive NVIDIA fixes # This addresses GSP issues, mesh shaders, OpenGL problems, and other NVIDIA issues -set -e # Exit on any error +set -e # Exit on any error # Default to non-interactive mode INTERACTIVE_MODE=false # Parse command line arguments while [[ $# -gt 0 ]]; do - case $1 in - -i|--interactive) - INTERACTIVE_MODE=true - shift - ;; - -h|--help) - echo "Usage: $0 [OPTIONS]" - echo "Options:" - echo " -i, --interactive Enable interactive prompts (default: auto-yes)" - echo " -h, --help Show this help message" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Use -h or --help for usage information" - exit 1 - ;; - esac + case $1 in + -i | --interactive) + INTERACTIVE_MODE=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " -i, --interactive Enable interactive prompts (default: auto-yes)" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac done # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to modify system files." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to modify system files." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Check for sudo privileges after argument parsing @@ -47,15 +47,15 @@ echo "==================================================" echo "Current Date: $(date)" echo "User: $USER" echo "Original user: ${SUDO_USER:-$USER}" -if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" +if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Mode: Interactive (prompts enabled)" else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" + echo "Mode: Automatic (auto-yes, use --interactive for prompts)" fi # Check if nvidia module is loaded if ! lsmod | grep -q nvidia; then - echo "Warning: NVIDIA module not currently loaded" + echo "Warning: NVIDIA module not currently loaded" fi # Create modprobe configuration directory if it doesn't exist @@ -78,32 +78,32 @@ echo "✓ Configuration written to: $CONFIG_FILE" # Function to backup file if it exists backup_file() { - local file="$1" - if [[ -f "$file" ]]; then - cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)" - echo "✓ Backed up $file" - fi + local file="$1" + if [[ -f $file ]]; then + cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)" + echo "✓ Backed up $file" + fi } # Function to add or update xorg.conf for RenderAccel configure_xorg() { - echo "" - echo "2. Configuring Xorg Settings..." - echo "===============================" - - XORG_CONF="/etc/X11/xorg.conf" - XORG_CONF_D="/etc/X11/xorg.conf.d" - NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf" - - # Create xorg.conf.d directory if it doesn't exist - mkdir -p "$XORG_CONF_D" - - # Backup existing xorg.conf if it exists - backup_file "$XORG_CONF" - backup_file "$NVIDIA_CONF" - - # Create NVIDIA-specific configuration - cat > "$NVIDIA_CONF" << EOF + echo "" + echo "2. Configuring Xorg Settings..." + echo "===============================" + + XORG_CONF="/etc/X11/xorg.conf" + XORG_CONF_D="/etc/X11/xorg.conf.d" + NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf" + + # Create xorg.conf.d directory if it doesn't exist + mkdir -p "$XORG_CONF_D" + + # Backup existing xorg.conf if it exists + backup_file "$XORG_CONF" + backup_file "$NVIDIA_CONF" + + # Create NVIDIA-specific configuration + cat > "$NVIDIA_CONF" << EOF # NVIDIA configuration with RenderAccel disabled # Created by nvidia_troubleshoot.sh on $(date) Section "Device" @@ -112,103 +112,107 @@ Section "Device" Option "RenderAccel" "false" EndSection EOF - - echo "✓ Created $NVIDIA_CONF with RenderAccel disabled" + + echo "✓ Created $NVIDIA_CONF with RenderAccel disabled" } # Function to add GCC mismatch workaround configure_gcc_workaround() { - echo "" - echo "3. Configuring GCC Mismatch Workaround..." - echo "==========================================" - - PROFILE_FILE="/etc/profile" - backup_file "$PROFILE_FILE" - - # Check if IGNORE_CC_MISMATCH is already set - if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then - echo "" >> "$PROFILE_FILE" - echo "# NVIDIA GCC version mismatch workaround" >> "$PROFILE_FILE" - echo "# Added by nvidia_troubleshoot.sh on $(date)" >> "$PROFILE_FILE" - echo "export IGNORE_CC_MISMATCH=1" >> "$PROFILE_FILE" - echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE" - else - echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE" - fi + echo "" + echo "3. Configuring GCC Mismatch Workaround..." + echo "==========================================" + + local PROFILE_FILE="/etc/profile" + local timestamp + timestamp=$(date) + backup_file "$PROFILE_FILE" + + # Check if IGNORE_CC_MISMATCH is already set + if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then + { + printf '\n' + printf '# NVIDIA GCC version mismatch workaround\n' + printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp" + printf 'export IGNORE_CC_MISMATCH=1\n' + } >> "$PROFILE_FILE" + echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE" + else + echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE" + fi } # Function to install pyroveil for mesh shader issues install_pyroveil() { - echo "" - echo "4. Pyroveil Setup for Mesh Shader Issues..." - echo "===========================================" - - local user_home="/home/$SUDO_USER" - local pyroveil_dir="$user_home/pyroveil" - - echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games" - echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems." - echo "" - - local install_pyroveil=true - - if [[ "$INTERACTIVE_MODE" == "true" ]]; then - read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - install_pyroveil=false - fi - else - echo "Auto-installing Pyroveil (use --interactive to prompt)" + echo "" + echo "4. Pyroveil Setup for Mesh Shader Issues..." + echo "===========================================" + + local user_home="/home/$SUDO_USER" + local pyroveil_dir="$user_home/pyroveil" + + echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games" + echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems." + echo "" + + local install_pyroveil=true + + if [[ $INTERACTIVE_MODE == "true" ]]; then + read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + install_pyroveil=false fi - - if [[ "$install_pyroveil" == "true" ]]; then - # Check for required dependencies - local missing_deps=() - - for dep in git cmake ninja gcc; do - if ! command -v $dep &> /dev/null; then - missing_deps+=($dep) - fi - done - - if [[ ${#missing_deps[@]} -gt 0 ]]; then - echo "Missing dependencies: ${missing_deps[*]}" - echo "Please install them first. On Arch Linux:" - echo "pacman -S base-devel git cmake ninja" - return 1 - fi - - # Clone and build pyroveil as the original user - echo "Installing Pyroveil to $pyroveil_dir..." - - if [[ -d "$pyroveil_dir" ]]; then - echo "Pyroveil directory already exists. Updating..." - sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull" - else - sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir" - fi - - sudo -u "$SUDO_USER" bash -c " + else + echo "Auto-installing Pyroveil (use --interactive to prompt)" + fi + + if [[ $install_pyroveil == "true" ]]; then + # Check for required dependencies + local missing_deps=() + + for dep in git cmake ninja gcc; do + if ! command -v "$dep" &> /dev/null; then + missing_deps+=("$dep") + fi + done + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + echo "Missing dependencies: ${missing_deps[*]}" + echo "Please install them first. On Arch Linux:" + echo "pacman -S base-devel git cmake ninja" + return 1 + fi + + # Clone and build pyroveil as the original user + echo "Installing Pyroveil to $pyroveil_dir..." + + if [[ -d $pyroveil_dir ]]; then + echo "Pyroveil directory already exists. Updating..." + sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull" + else + sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir" + fi + + sudo -u "$SUDO_USER" bash -c " cd '$pyroveil_dir' git submodule update --init cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local ninja -C build install " - - echo "✓ Pyroveil installed successfully" - echo "" - echo "To use Pyroveil with games that have mesh shader issues:" - echo "1. For Final Fantasy VII Rebirth:" - echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%" - echo "" - echo "2. For Steam games, add to launch options:" - echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%" - echo "" - echo "Available configs in: $pyroveil_dir/hacks/" - - # Create a helper script - cat > "$user_home/run-with-pyroveil.sh" << EOF + + echo "✓ Pyroveil installed successfully" + echo "" + echo "To use Pyroveil with games that have mesh shader issues:" + echo "1. For Final Fantasy VII Rebirth:" + echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%" + echo "" + echo "2. For Steam games, add to launch options:" + echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%" + echo "" + echo "Available configs in: $pyroveil_dir/hacks/" + + # Create a helper script + cat > "$user_home/run-with-pyroveil.sh" << EOF #!/bin/bash # Helper script to run games with Pyroveil # Usage: ./run-with-pyroveil.sh @@ -233,88 +237,89 @@ echo "Config file: \$PYROVEIL_CONFIG" exec "\$@" EOF - - chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh" - chmod +x "$user_home/run-with-pyroveil.sh" - echo "✓ Created helper script: $user_home/run-with-pyroveil.sh" - - else - echo "Skipping Pyroveil installation" - echo "Note: You can manually install it later for mesh shader issues" - fi + + chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh" + chmod +x "$user_home/run-with-pyroveil.sh" + echo "✓ Created helper script: $user_home/run-with-pyroveil.sh" + + else + echo "Skipping Pyroveil installation" + echo "Note: You can manually install it later for mesh shader issues" + fi } # Function to check for kernel parameter modifications suggest_kernel_params() { - echo "" - echo "5. Kernel Parameter Recommendations..." - echo "=====================================" - - echo "NVIDIA Driver Issues and Recommended Kernel Parameters:" - echo "" - echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors" - echo " (especially with nvidia-96xx drivers):" - echo " → Add 'nopat' to kernel parameters" - echo "" - echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:" - echo " → Consider disabling micro-op cache in BIOS settings" - echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs" - echo " → Helps with severe graphical glitches in Xwayland applications" - echo " → Note: Disabling micro-op cache reduces CPU performance" - echo "" - echo "To add kernel parameters:" - echo "1. Edit /etc/default/grub" - echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT" - echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg" - echo "4. Reboot" - echo "" - echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:" - echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"' - - # Check current CPU for micro-op cache relevance - echo "" - echo "CPU Information (for micro-op cache consideration):" - if command -v lscpu &> /dev/null; then - local cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs) - echo "Current CPU: $cpu_info" - - if echo "$cpu_info" | grep -qi "intel"; then - echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache" - elif echo "$cpu_info" | grep -qi "amd"; then - echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache" - fi + echo "" + echo "5. Kernel Parameter Recommendations..." + echo "=====================================" + + echo "NVIDIA Driver Issues and Recommended Kernel Parameters:" + echo "" + echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors" + echo " (especially with nvidia-96xx drivers):" + echo " → Add 'nopat' to kernel parameters" + echo "" + echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:" + echo " → Consider disabling micro-op cache in BIOS settings" + echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs" + echo " → Helps with severe graphical glitches in Xwayland applications" + echo " → Note: Disabling micro-op cache reduces CPU performance" + echo "" + echo "To add kernel parameters:" + echo "1. Edit /etc/default/grub" + echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT" + echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg" + echo "4. Reboot" + echo "" + echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:" + echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"' + + # Check current CPU for micro-op cache relevance + echo "" + echo "CPU Information (for micro-op cache consideration):" + if command -v lscpu &> /dev/null; then + local cpu_info + cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs) + echo "Current CPU: $cpu_info" + + if echo "$cpu_info" | grep -qi "intel"; then + echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache" + elif echo "$cpu_info" | grep -qi "amd"; then + echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache" fi + fi } # Function to suggest desktop environment settings suggest_desktop_settings() { + echo "" + echo "6. Desktop Environment Recommendations..." + echo "========================================" + + echo "For fullscreen application freezing/crashing issues:" + echo "" + echo "Enable Display Compositing and Direct fullscreen rendering:" + echo "" + echo "• KDE Plasma:" + echo " System Settings → Display and Monitor → Compositor" + echo " → Enable compositor + Enable direct rendering for fullscreen windows" + echo "" + echo "• GNOME:" + echo " Use Extensions or dconf-editor to enable compositing features" + echo "" + echo "• XFCE:" + echo " Settings → Window Manager Tweaks → Compositor" + echo " → Enable display compositing" + echo "" + echo "• Cinnamon:" + echo " System Settings → Effects → Enable desktop effects" + + # Detect current desktop environment + if [[ -n $XDG_CURRENT_DESKTOP ]]; then echo "" - echo "6. Desktop Environment Recommendations..." - echo "========================================" - - echo "For fullscreen application freezing/crashing issues:" - echo "" - echo "Enable Display Compositing and Direct fullscreen rendering:" - echo "" - echo "• KDE Plasma:" - echo " System Settings → Display and Monitor → Compositor" - echo " → Enable compositor + Enable direct rendering for fullscreen windows" - echo "" - echo "• GNOME:" - echo " Use Extensions or dconf-editor to enable compositing features" - echo "" - echo "• XFCE:" - echo " Settings → Window Manager Tweaks → Compositor" - echo " → Enable display compositing" - echo "" - echo "• Cinnamon:" - echo " System Settings → Effects → Enable desktop effects" - - # Detect current desktop environment - if [[ -n "$XDG_CURRENT_DESKTOP" ]]; then - echo "" - echo "Detected desktop environment: $XDG_CURRENT_DESKTOP" - fi + echo "Detected desktop environment: $XDG_CURRENT_DESKTOP" + fi } # Apply all configurations @@ -327,13 +332,13 @@ echo "" echo "7. Regenerating Initramfs..." echo "============================" if command -v mkinitcpio &> /dev/null; then - mkinitcpio -P - echo "✓ Initramfs regenerated with mkinitcpio" + mkinitcpio -P + echo "✓ Initramfs regenerated with mkinitcpio" elif command -v dracut &> /dev/null; then - dracut --force - echo "✓ Initramfs regenerated with dracut" + dracut --force + echo "✓ Initramfs regenerated with dracut" else - echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs." + echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs." fi # Display all recommendations @@ -349,7 +354,7 @@ echo "✓ GSP firmware disabled" echo "✓ RenderAccel disabled in Xorg configuration" echo "✓ GCC version mismatch workaround added" if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then - echo "✓ Pyroveil installed for mesh shader issues" + echo "✓ Pyroveil installed for mesh shader issues" fi echo "✓ Initramfs regenerated" echo "" @@ -359,4 +364,4 @@ echo "• Configure desktop environment compositing settings" echo "• Add kernel parameters if needed (nopat for memory issues)" echo "" echo "IMPORTANT: You must reboot for changes to take effect!" -echo "After reboot, verify GSP with: cat /proc/driver/nvidia/params | grep EnableGpuFirmware" \ No newline at end of file +echo "After reboot, verify GSP with: cat /proc/driver/nvidia/params | grep EnableGpuFirmware" diff --git a/scripts/meta/shell_check.sh b/scripts/meta/shell_check.sh index 8b7d327..6fd4be7 100755 --- a/scripts/meta/shell_check.sh +++ b/scripts/meta/shell_check.sh @@ -25,24 +25,20 @@ INSTALL_ONLY="false" LIST_ONLY="false" VERBOSE="false" -log() { - printf '%s\n' "$*" -} - log_info() { - printf '\033[1;34m[INFO]\033[0m %s\n' "$*" + printf '\033[1;34m[INFO]\033[0m %s\n' "$*" } log_warn() { - printf '\033[1;33m[WARN]\033[0m %s\n' "$*" + printf '\033[1;33m[WARN]\033[0m %s\n' "$*" } log_error() { - printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 + printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 } usage() { - cat </dev/null 2>&1; } +is_cmd() { command -v "$1" > /dev/null 2>&1; } is_arch() { is_cmd pacman; } have_aur_helper() { is_cmd yay || is_cmd paru; } install_if_missing() { - local pkg cmd - pkg="$1"; cmd="$2" - if is_cmd "$cmd"; then - [[ "$VERBOSE" == "true" ]] && log_info "Found $cmd" - return 0 - fi + local pkg cmd + pkg="$1" + cmd="$2" + if is_cmd "$cmd"; then + [[ $VERBOSE == "true" ]] && log_info "Found $cmd" + return 0 + fi - if [[ "$SKIP_INSTALL" == "true" ]]; then - log_warn "Skipping install of $pkg ($cmd not found)" - return 1 - fi + if [[ $SKIP_INSTALL == "true" ]]; then + log_warn "Skipping install of $pkg ($cmd not found)" + return 1 + fi - if is_arch; then - log_info "Installing $pkg via pacman..." - if ! sudo pacman -S --needed --noconfirm "$pkg"; then - log_warn "Failed to install $pkg via pacman." - return 1 - fi - return 0 - else - log_warn "Non-Arch system detected. Please install '$pkg' manually." - return 1 - fi + if is_arch; then + log_info "Installing $pkg via pacman..." + if ! sudo pacman -S --needed --noconfirm "$pkg"; then + log_warn "Failed to install $pkg via pacman." + return 1 + fi + return 0 + else + log_warn "Non-Arch system detected. Please install '$pkg' manually." + return 1 + fi } install_linters() { - local ok=0 + local ok=0 - # Core linters - install_if_missing shellcheck shellcheck || ok=1 - install_if_missing shfmt shfmt || ok=1 + # Core linters + install_if_missing shellcheck shellcheck || ok=1 + install_if_missing shfmt shfmt || ok=1 - # Optional linters (best-effort) - # checkbashisms may be in repos or AUR; try pacman first, then AUR helper - if ! is_cmd checkbashisms; then - if is_arch; then - if ! sudo pacman -S --needed --noconfirm checkbashisms 2>/dev/null; then - if have_aur_helper; then - log_info "Installing checkbashisms from AUR (requires yay/paru)..." - if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi - if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi - else - log_warn "checkbashisms not installed (no AUR helper)." - fi - fi - fi - fi + # Optional linters (best-effort) + # checkbashisms may be in repos or AUR; try pacman first, then AUR helper + if ! is_cmd checkbashisms; then + if is_arch; then + if ! sudo pacman -S --needed --noconfirm checkbashisms 2> /dev/null; then + if have_aur_helper; then + log_info "Installing checkbashisms from AUR (requires yay/paru)..." + if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi + if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi + else + log_warn "checkbashisms not installed (no AUR helper)." + fi + fi + fi + fi - # bashate (python-based), typically available as python-bashate in AUR - if ! is_cmd bashate; then - if is_arch && have_aur_helper; then - log_info "Installing bashate from AUR (requires yay/paru)..." - if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi - if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi - else - # Try pip if user has it and wants to - if is_cmd pipx; then - log_info "Installing bashate via pipx..." - pipx install bashate || true - elif is_cmd pip3; then - log_info "Installing bashate via pip (user)..." - pip3 install --user bashate || true - else - log_warn "bashate not installed (no AUR helper or pip available)." - fi - fi - fi + # bashate (python-based), typically available as python-bashate in AUR + if ! is_cmd bashate; then + if is_arch && have_aur_helper; then + log_info "Installing bashate from AUR (requires yay/paru)..." + if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi + if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi + else + # Try pip if user has it and wants to + if is_cmd pipx; then + log_info "Installing bashate via pipx..." + pipx install bashate || true + elif is_cmd pip3; then + log_info "Installing bashate via pip (user)..." + pip3 install --user bashate || true + else + log_warn "bashate not installed (no AUR helper or pip available)." + fi + fi + fi - return "$ok" + return "$ok" } TMPDIR=$(mktemp -d) -cleanup() { rm -rf "$TMPDIR"; } -trap cleanup EXIT +trap 'rm -rf "${TMPDIR:-}"' EXIT ABS_FILES_Z="$TMPDIR/files_abs.zlist" REL_FILES_Z="$TMPDIR/files_rel.zlist" discover_shell_files() { - local base="$1" - local -a all - all=() + local base="$1" + local -a all + all=() - if git -C "$base" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files -z) - while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z) - else - while IFS= read -r -d '' f; do - # trim leading ./ to keep consistent style with git paths - f="${f#./}" - f="${f#${base}/}" - all+=("$f") - done < <(find "$base" -type f -print0) - fi + if git -C "$base" rev-parse --is-inside-work-tree > /dev/null 2>&1; then + while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files -z) + while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z) + else + while IFS= read -r -d '' f; do + # trim leading ./ to keep consistent style with git paths + f="${f#./}" + f="${f#"${base}"/}" + all+=("$f") + done < <(find "$base" -type f -print0) + fi - local -a shells - shells=() + local -a shells + shells=() - for rel in "${all[@]}"; do - # skip binary-ish or huge files quickly by extension heuristic - case "$rel" in - *.png|*.jpg|*.jpeg|*.gif|*.ico|*.pdf|*.svg|*.zip|*.tar|*.gz|*.xz|*.7z|*.so|*.o|*.bin) - continue ;; - esac + for rel in "${all[@]}"; do + # skip binary-ish or huge files quickly by extension heuristic + case "$rel" in + *.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin) + continue + ;; + esac - local abs="$base/$rel" - [[ -f "$abs" && -r "$abs" ]] || continue + local abs="$base/$rel" + [[ -f $abs && -r $abs ]] || continue - if [[ "$rel" == *.sh || "$rel" == *.bash || "$rel" == *.zsh ]]; then - shells+=("$rel") - continue - fi + if [[ $rel == *.sh || $rel == *.bash || $rel == *.zsh ]]; then + shells+=("$rel") + continue + fi - # Check shebang - local first - first=$(head -n 1 -- "$abs" 2>/dev/null || true) - if [[ "$first" =~ ^#! && "$first" =~ (ba|z|d|k)?sh ]]; then - shells+=("$rel") - continue - fi + # Check shebang + local first + first=$(head -n 1 -- "$abs" 2> /dev/null || true) + if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then + shells+=("$rel") + continue + fi - # Also catch executable files with shell shebang even without extension - if [[ -x "$abs" ]]; then - if [[ "$first" =~ ^#! && "$first" =~ (ba|z|d|k)?sh ]]; then - shells+=("$rel") - fi - fi - done + # Also catch executable files with shell shebang even without extension + if [[ -x $abs ]]; then + if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then + shells+=("$rel") + fi + fi + done - # write lists - : >"$REL_FILES_Z" - : >"$ABS_FILES_Z" - for rel in "${shells[@]}"; do - printf '%s\0' "$rel" >> "$REL_FILES_Z" - printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z" - done + # write lists + : > "$REL_FILES_Z" + : > "$ABS_FILES_Z" + for rel in "${shells[@]}"; do + printf '%s\0' "$rel" >> "$REL_FILES_Z" + printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z" + done } print_file_list() { - local count - count=$(tr -cd '\0' < "$REL_FILES_Z" | wc -c) - log_info "Discovered $count shell file(s) under $ROOT_DIR" - if [[ "$VERBOSE" == "true" ]]; then - tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /' - fi + local count + count=$(tr -cd '\0' < "$REL_FILES_Z" | wc -c) + log_info "Discovered $count shell file(s) under $ROOT_DIR" + if [[ $VERBOSE == "true" ]]; then + tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /' + fi } run_linters() { - local issues=0 - local count - count=$(tr -cd '\0' < "$ABS_FILES_Z" | wc -c) - if [[ "$count" -eq 0 ]]; then - log_warn "No shell files found to lint." - return 0 - fi + local issues=0 + local count + count=$(tr -cd '\0' < "$ABS_FILES_Z" | wc -c) + if [[ $count -eq 0 ]]; then + log_warn "No shell files found to lint." + return 0 + fi - mapfile -d '' -t FILES < "$ABS_FILES_Z" + mapfile -d '' -t FILES < "$ABS_FILES_Z" - log_info "Running shellcheck..." - local sc_out="$TMPDIR/shellcheck.txt" - if is_cmd shellcheck; then - if ! shellcheck -x -S style "${FILES[@]}" >"$sc_out" 2>&1; then - issues=$((issues+1)) - fi - else - log_warn "shellcheck not found; skipping" - fi + log_info "Running shellcheck..." + local sc_out="$TMPDIR/shellcheck.txt" + if is_cmd shellcheck; then + if ! shellcheck -x -S style "${FILES[@]}" > "$sc_out" 2>&1; then + issues=$((issues + 1)) + fi + else + log_warn "shellcheck not found; skipping" + fi - log_info "Running shfmt (diff mode)..." - local shfmt_out="$TMPDIR/shfmt.diff" - if is_cmd shfmt; then - if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" >"$shfmt_out" 2>&1; then - # shfmt returns non-zero when diff exists - issues=$((issues+1)) - fi - else - log_warn "shfmt not found; skipping" - fi + log_info "Running shfmt (diff mode)..." + local shfmt_out="$TMPDIR/shfmt.diff" + if is_cmd shfmt; then + if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" > "$shfmt_out" 2>&1; then + # shfmt returns non-zero when diff exists + issues=$((issues + 1)) + fi + else + log_warn "shfmt not found; skipping" + fi - log_info "Running checkbashisms (optional)..." - local cbi_out="$TMPDIR/checkbashisms.txt" - if is_cmd checkbashisms; then - # checkbashisms exits 0 if OK, 1 if issues - if ! checkbashisms "${FILES[@]}" >"$cbi_out" 2>&1; then - issues=$((issues+1)) - fi - else - log_warn "checkbashisms not found; skipping" - fi + log_info "Running checkbashisms (optional)..." + local cbi_out="$TMPDIR/checkbashisms.txt" + local cbi_status=0 + if is_cmd checkbashisms; then + # checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings + checkbashisms "${FILES[@]}" > "$cbi_out" 2>&1 + cbi_status=$? + if [[ $cbi_status -eq 1 ]]; then + issues=$((issues + 1)) + elif [[ $cbi_status -ne 0 ]]; then + log_warn "checkbashisms exited with status $cbi_status (treated as warning)" + fi + else + log_warn "checkbashisms not found; skipping" + fi - log_info "Running bash/zsh/sh syntax checks (-n)..." - local bash_out="$TMPDIR/bash_syntax.txt" - local zsh_out="$TMPDIR/zsh_syntax.txt" - local sh_out="$TMPDIR/sh_syntax.txt" + log_info "Running bash/zsh/sh syntax checks (-n)..." + local bash_out="$TMPDIR/bash_syntax.txt" + local zsh_out="$TMPDIR/zsh_syntax.txt" + local sh_out="$TMPDIR/sh_syntax.txt" - # Partition files by shebang for better accuracy - local -a BASH_FILES ZSH_FILES SH_FILES - BASH_FILES=(); ZSH_FILES=(); SH_FILES=() - for f in "${FILES[@]}"; do - local first - first=$(head -n 1 -- "$f" 2>/dev/null || true) - if [[ "$first" =~ bash ]]; then - BASH_FILES+=("$f") - elif [[ "$first" =~ zsh ]]; then - ZSH_FILES+=("$f") - else - SH_FILES+=("$f") - fi - done + # Partition files by shebang for better accuracy + local -a BASH_FILES ZSH_FILES SH_FILES + BASH_FILES=() + ZSH_FILES=() + SH_FILES=() + for f in "${FILES[@]}"; do + local first + first=$(head -n 1 -- "$f" 2> /dev/null || true) + if [[ $first =~ bash ]]; then + BASH_FILES+=("$f") + elif [[ $first =~ zsh ]]; then + ZSH_FILES+=("$f") + else + SH_FILES+=("$f") + fi + done - if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then - if ! bash -n "${BASH_FILES[@]}" 2>"$bash_out"; then - issues=$((issues+1)) - fi - fi - if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then - if ! zsh -n "${ZSH_FILES[@]}" 2>"$zsh_out"; then - issues=$((issues+1)) - fi - fi - # prefer dash if present for /bin/sh style - if [[ ${#SH_FILES[@]} -gt 0 ]]; then - if is_cmd dash; then - if ! dash -n "${SH_FILES[@]}" 2>"$sh_out"; then - issues=$((issues+1)) - fi - elif is_cmd sh; then - if ! sh -n "${SH_FILES[@]}" 2>"$sh_out"; then - issues=$((issues+1)) - fi - fi - fi + if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then + if ! bash -n "${BASH_FILES[@]}" 2> "$bash_out"; then + issues=$((issues + 1)) + fi + fi + if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then + if ! zsh -n "${ZSH_FILES[@]}" 2> "$zsh_out"; then + issues=$((issues + 1)) + fi + fi + # prefer dash if present for /bin/sh style + if [[ ${#SH_FILES[@]} -gt 0 ]]; then + if is_cmd dash; then + if ! dash -n "${SH_FILES[@]}" 2> "$sh_out"; then + issues=$((issues + 1)) + fi + elif is_cmd sh; then + if ! sh -n "${SH_FILES[@]}" 2> "$sh_out"; then + issues=$((issues + 1)) + fi + fi + fi - echo - log_info "========== Shell Lint Report ==========" + echo + log_info "========== Shell Lint Report ==========" - if [[ -s "$sc_out" ]]; then - printf '\n\033[1m-- shellcheck --\033[0m\n' - cat "$sc_out" - else - printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n' - fi + if [[ -s $sc_out ]]; then + printf '\n\033[1m-- shellcheck --\033[0m\n' + cat "$sc_out" + else + printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n' + fi - if [[ -s "$shfmt_out" ]]; then - printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n' - cat "$shfmt_out" - else - printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n' - fi + if [[ -s $shfmt_out ]]; then + printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n' + cat "$shfmt_out" + else + printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n' + fi - if [[ -s "$cbi_out" ]]; then - printf '\n\033[1m-- checkbashisms --\033[0m\n' - cat "$cbi_out" - else - printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n' - fi + if [[ -s $cbi_out ]]; then + printf '\n\033[1m-- checkbashisms --\033[0m\n' + cat "$cbi_out" + else + printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n' + fi - if [[ -s "$bash_out" ]]; then - printf '\n\033[1m-- bash -n (syntax) --\033[0m\n' - cat "$bash_out" - else - printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $bash_out ]]; then + printf '\n\033[1m-- bash -n (syntax) --\033[0m\n' + cat "$bash_out" + else + printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n' + fi - if [[ -s "$zsh_out" ]]; then - printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n' - cat "$zsh_out" - else - printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $zsh_out ]]; then + printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n' + cat "$zsh_out" + else + printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n' + fi - if [[ -s "$sh_out" ]]; then - printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n' - cat "$sh_out" - else - printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $sh_out ]]; then + printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n' + cat "$sh_out" + else + printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n' + fi - echo - if [[ $issues -gt 0 ]]; then - log_error "Linting completed with $issues tool(s) reporting issues." - return 1 - else - log_info "All checks passed." - return 0 - fi + echo + if [[ $issues -gt 0 ]]; then + log_error "Linting completed with $issues tool(s) reporting issues." + return 1 + else + log_info "All checks passed." + return 0 + fi } # Main -if [[ "$INSTALL_ONLY" == "true" ]]; then - install_linters - exit $? +if [[ $INSTALL_ONLY == "true" ]]; then + install_linters + exit $? fi # Only attempt installs if not list-only -if [[ "$LIST_ONLY" != "true" ]]; then - install_linters || true +if [[ $LIST_ONLY != "true" ]]; then + install_linters || true fi discover_shell_files "$ROOT_DIR" print_file_list -if [[ "$LIST_ONLY" == "true" ]]; then - exit 0 +if [[ $LIST_ONLY == "true" ]]; then + exit 0 fi run_linters exit $? - diff --git a/scripts/setup_periodic_system.sh b/scripts/setup_periodic_system.sh index c24bc4b..abc4900 100755 --- a/scripts/setup_periodic_system.sh +++ b/scripts/setup_periodic_system.sh @@ -3,40 +3,40 @@ # Executes every hour and on system startup # Handles sudo privileges automatically -set -e # Exit on any error +set -e # Exit on any error # Default to non-interactive mode INTERACTIVE_MODE=false # Parse command line arguments while [[ $# -gt 0 ]]; do - case $1 in - -i|--interactive) - INTERACTIVE_MODE=true - shift - ;; - -h|--help) - echo "Usage: $0 [OPTIONS]" - echo "Options:" - echo " -i, --interactive Enable interactive prompts (default: auto-yes)" - echo " -h, --help Show this help message" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Use -h or --help for usage information" - exit 1 - ;; - esac + case $1 in + -i | --interactive) + INTERACTIVE_MODE=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " -i, --interactive Enable interactive prompts (default: auto-yes)" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac done # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to create systemd services and timers." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to create systemd services and timers." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Check for sudo privileges after argument parsing @@ -47,10 +47,10 @@ echo "===================================================" echo "Current Date: $(date)" echo "User: $USER" echo "Original user: ${SUDO_USER:-$USER}" -if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" +if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Mode: Interactive (prompts enabled)" else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" + echo "Mode: Automatic (auto-yes, use --interactive for prompts)" fi # Get the directory where this script is located @@ -86,231 +86,231 @@ TEMPLATE_LOGROTATE="$LOGROTATE_TEMPLATES/periodic-system-maintenance" # Function to verify required files exist verify_files() { - echo "" - echo "1. Verifying Required Files..." - echo "==============================" - - local missing_files=() - - if [[ ! -f "$PACMAN_WRAPPER_SCRIPT" ]]; then - missing_files+=("$PACMAN_WRAPPER_SCRIPT") - fi - - if [[ ! -f "$PACMAN_WRAPPER_INSTALL" ]]; then - missing_files+=("$PACMAN_WRAPPER_INSTALL") - fi - - if [[ ! -f "$HOSTS_INSTALL_SCRIPT" ]]; then - missing_files+=("$HOSTS_INSTALL_SCRIPT") - fi - - # Check template files as well - for tmpl in \ - "$TEMPLATE_MAINT_SCRIPT" \ - "$TEMPLATE_HOSTS_MONITOR" \ - "$TEMPLATE_BROWSER_WRAPPER" \ - "$TEMPLATE_SVC_MAINT" \ - "$TEMPLATE_TIMER" \ - "$TEMPLATE_STARTUP" \ - "$TEMPLATE_HOSTS_SVC" \ - "$TEMPLATE_LOGROTATE"; do - if [[ ! -f "$tmpl" ]]; then - missing_files+=("$tmpl") - fi - done + echo "" + echo "1. Verifying Required Files..." + echo "==============================" - if [[ ${#missing_files[@]} -gt 0 ]]; then - echo "Error: The following required files are missing:" - for file in "${missing_files[@]}"; do - echo " - $file" - done - exit 1 + local missing_files=() + + if [[ ! -f $PACMAN_WRAPPER_SCRIPT ]]; then + missing_files+=("$PACMAN_WRAPPER_SCRIPT") + fi + + if [[ ! -f $PACMAN_WRAPPER_INSTALL ]]; then + missing_files+=("$PACMAN_WRAPPER_INSTALL") + fi + + if [[ ! -f $HOSTS_INSTALL_SCRIPT ]]; then + missing_files+=("$HOSTS_INSTALL_SCRIPT") + fi + + # Check template files as well + for tmpl in \ + "$TEMPLATE_MAINT_SCRIPT" \ + "$TEMPLATE_HOSTS_MONITOR" \ + "$TEMPLATE_BROWSER_WRAPPER" \ + "$TEMPLATE_SVC_MAINT" \ + "$TEMPLATE_TIMER" \ + "$TEMPLATE_STARTUP" \ + "$TEMPLATE_HOSTS_SVC" \ + "$TEMPLATE_LOGROTATE"; do + if [[ ! -f $tmpl ]]; then + missing_files+=("$tmpl") fi - - echo "✓ All required files found" + done + + if [[ ${#missing_files[@]} -gt 0 ]]; then + echo "Error: The following required files are missing:" + for file in "${missing_files[@]}"; do + echo " - $file" + done + exit 1 + fi + + echo "✓ All required files found" } # Function to create the combined execution script create_execution_script() { - echo "" - echo "2. Creating Combined Execution Script..." - echo "=======================================" - - local exec_script="/usr/local/bin/periodic-system-maintenance.sh" + echo "" + echo "2. Creating Combined Execution Script..." + echo "=======================================" - # Install from template with path substitutions - sed \ - -e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \ - -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ - "$TEMPLATE_MAINT_SCRIPT" > "$exec_script" + local exec_script="/usr/local/bin/periodic-system-maintenance.sh" - chmod +x "$exec_script" - echo "✓ Installed execution script from template: $exec_script" + # Install from template with path substitutions + sed \ + -e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \ + -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ + "$TEMPLATE_MAINT_SCRIPT" > "$exec_script" + + chmod +x "$exec_script" + echo "✓ Installed execution script from template: $exec_script" } # Function to create systemd service create_systemd_service() { - echo "" - echo "3. Creating Systemd Service..." - echo "=============================" - - local service_file="/etc/systemd/system/periodic-system-maintenance.service" - install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file" - echo "✓ Installed systemd service from template: $service_file" + echo "" + echo "3. Creating Systemd Service..." + echo "=============================" + + local service_file="/etc/systemd/system/periodic-system-maintenance.service" + install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file" + echo "✓ Installed systemd service from template: $service_file" } # Function to create systemd timer for hourly execution create_systemd_timer() { - echo "" - echo "4. Creating Systemd Timer..." - echo "============================" - - local timer_file="/etc/systemd/system/periodic-system-maintenance.timer" - install -m 0644 "$TEMPLATE_TIMER" "$timer_file" - echo "✓ Installed systemd timer from template: $timer_file" + echo "" + echo "4. Creating Systemd Timer..." + echo "============================" + + local timer_file="/etc/systemd/system/periodic-system-maintenance.timer" + install -m 0644 "$TEMPLATE_TIMER" "$timer_file" + echo "✓ Installed systemd timer from template: $timer_file" } # Function to create startup service (additional to timer) create_startup_service() { - echo "" - echo "5. Creating Startup Service..." - echo "==============================" - - local startup_service="/etc/systemd/system/periodic-system-startup.service" - install -m 0644 "$TEMPLATE_STARTUP" "$startup_service" - echo "✓ Installed startup service from template: $startup_service" + echo "" + echo "5. Creating Startup Service..." + echo "==============================" + + local startup_service="/etc/systemd/system/periodic-system-startup.service" + install -m 0644 "$TEMPLATE_STARTUP" "$startup_service" + echo "✓ Installed startup service from template: $startup_service" } # Function to create hosts file monitor service create_hosts_monitor_service() { - echo "" - echo "6. Creating Hosts File Monitor Service..." - echo "========================================" - - local monitor_script="/usr/local/bin/hosts-file-monitor.sh" - local monitor_service="/etc/systemd/system/hosts-file-monitor.service" + echo "" + echo "6. Creating Hosts File Monitor Service..." + echo "========================================" - # Install the monitor script from template with substitution - sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ - "$TEMPLATE_HOSTS_MONITOR" > "$monitor_script" - chmod +x "$monitor_script" - echo "✓ Installed hosts monitor script from template: $monitor_script" + local monitor_script="/usr/local/bin/hosts-file-monitor.sh" + local monitor_service="/etc/systemd/system/hosts-file-monitor.service" - # Install the systemd service from template - install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service" - echo "✓ Installed hosts monitor service from template: $monitor_service" + # Install the monitor script from template with substitution + sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ + "$TEMPLATE_HOSTS_MONITOR" > "$monitor_script" + chmod +x "$monitor_script" + echo "✓ Installed hosts monitor script from template: $monitor_script" + + # Install the systemd service from template + install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service" + echo "✓ Installed hosts monitor service from template: $monitor_service" } # Function to install browser pre-exec wrapper and wire common browser names install_browser_preexec_wrapper() { - echo "" - echo "6.1 Installing Browser Pre-Exec Wrapper..." - echo "=========================================" + echo "" + echo "6.1 Installing Browser Pre-Exec Wrapper..." + echo "=========================================" - local wrapper="/usr/local/bin/browser-preexec-wrapper" - sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ - "$TEMPLATE_BROWSER_WRAPPER" > "$wrapper" - chmod +x "$wrapper" - echo "✓ Installed wrapper: $wrapper" + local wrapper="/usr/local/bin/browser-preexec-wrapper" + sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ + "$TEMPLATE_BROWSER_WRAPPER" > "$wrapper" + chmod +x "$wrapper" + echo "✓ Installed wrapper: $wrapper" - # Allow passwordless execution of hosts installer for root-only actions - local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd" - if command -v visudo >/dev/null 2>&1; then - echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file" - chmod 440 "$sudoers_file" - # Validate syntax - visudo -c >/dev/null || echo "Warning: sudoers validation returned non-zero" - echo "✓ Sudoers drop-in created: $sudoers_file" - else - echo "visudo not found; skipping sudoers drop-in" - fi + # Allow passwordless execution of hosts installer for root-only actions + local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd" + if command -v visudo > /dev/null 2>&1; then + echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file" + chmod 440 "$sudoers_file" + # Validate syntax + visudo -c > /dev/null || echo "Warning: sudoers validation returned non-zero" + echo "✓ Sudoers drop-in created: $sudoers_file" + else + echo "visudo not found; skipping sudoers drop-in" + fi - # Create symlinks for common browser commands to the wrapper in /usr/local/bin - # This takes precedence over /usr/bin in PATH on most systems. - local browsers=( "thorium-browser" "google-chrome" "google-chrome-stable" "chromium" "brave" "brave-browser" "vivaldi-stable" "firefox" ) - for b in "${browsers[@]}"; do - local link="/usr/local/bin/$b" - ln -sf "$wrapper" "$link" - done - echo "✓ Symlinked wrapper for common browsers in /usr/local/bin" + # Create symlinks for common browser commands to the wrapper in /usr/local/bin + # This takes precedence over /usr/bin in PATH on most systems. + local browsers=("thorium-browser" "google-chrome" "google-chrome-stable" "chromium" "brave" "brave-browser" "vivaldi-stable" "firefox") + for b in "${browsers[@]}"; do + local link="/usr/local/bin/$b" + ln -sf "$wrapper" "$link" + done + echo "✓ Symlinked wrapper for common browsers in /usr/local/bin" } # Function to enable and start services enable_services() { - echo "" - echo "7. Enabling Services and Timer..." - echo "=================================" - - # Reload systemd daemon - systemctl daemon-reload - echo "✓ Systemd daemon reloaded" - - # Enable and start the timer - systemctl enable periodic-system-maintenance.timer - systemctl start periodic-system-maintenance.timer - echo "✓ Timer enabled and started" - - # Enable startup service (but don't start it now) - systemctl enable periodic-system-startup.service - echo "✓ Startup service enabled" - - # Enable hosts file monitor service - systemctl enable hosts-file-monitor.service - systemctl start hosts-file-monitor.service - echo "✓ Hosts file monitor service enabled and started" - - # Show timer status - echo "" - echo "Timer Status:" - systemctl status periodic-system-maintenance.timer --no-pager -l - - echo "" - echo "Hosts Monitor Status:" - systemctl status hosts-file-monitor.service --no-pager -l - - echo "" - echo "Next scheduled runs:" - systemctl list-timers periodic-system-maintenance.timer --no-pager + echo "" + echo "7. Enabling Services and Timer..." + echo "=================================" + + # Reload systemd daemon + systemctl daemon-reload + echo "✓ Systemd daemon reloaded" + + # Enable and start the timer + systemctl enable periodic-system-maintenance.timer + systemctl start periodic-system-maintenance.timer + echo "✓ Timer enabled and started" + + # Enable startup service (but don't start it now) + systemctl enable periodic-system-startup.service + echo "✓ Startup service enabled" + + # Enable hosts file monitor service + systemctl enable hosts-file-monitor.service + systemctl start hosts-file-monitor.service + echo "✓ Hosts file monitor service enabled and started" + + # Show timer status + echo "" + echo "Timer Status:" + systemctl status periodic-system-maintenance.timer --no-pager -l + + echo "" + echo "Hosts Monitor Status:" + systemctl status hosts-file-monitor.service --no-pager -l + + echo "" + echo "Next scheduled runs:" + systemctl list-timers periodic-system-maintenance.timer --no-pager } # Function to create log rotation configuration create_log_rotation() { - echo "" - echo "8. Setting up Log Rotation..." - echo "=============================" - - local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance" - install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf" - echo "✓ Installed log rotation configuration from template: $logrotate_conf" + echo "" + echo "8. Setting up Log Rotation..." + echo "=============================" + + local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance" + install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf" + echo "✓ Installed log rotation configuration from template: $logrotate_conf" } # Function to run initial execution run_initial_execution() { - echo "" - echo "9. Running Initial Execution..." - echo "===============================" - - local run_initial=true - - if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Would you like to run the system maintenance now to test the setup?" - read -p "Run initial execution? (y/N): " -n 1 -r - echo - - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - run_initial=false - fi - else - echo "Auto-running initial execution to test the setup (use --interactive to prompt)" - fi - - if [[ "$run_initial" == "true" ]]; then - echo "Running initial system maintenance..." - /usr/local/bin/periodic-system-maintenance.sh - echo "✓ Initial execution completed" - else - echo "Skipping initial execution" + echo "" + echo "9. Running Initial Execution..." + echo "===============================" + + local run_initial=true + + if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Would you like to run the system maintenance now to test the setup?" + read -p "Run initial execution? (y/N): " -n 1 -r + echo + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + run_initial=false fi + else + echo "Auto-running initial execution to test the setup (use --interactive to prompt)" + fi + + if [[ $run_initial == "true" ]]; then + echo "Running initial system maintenance..." + /usr/local/bin/periodic-system-maintenance.sh + echo "✓ Initial execution completed" + else + echo "Skipping initial execution" + fi } # Main execution diff --git a/scripts/setup_thorium_startup.sh b/scripts/setup_thorium_startup.sh index 86e5f79..042a1f8 100755 --- a/scripts/setup_thorium_startup.sh +++ b/scripts/setup_thorium_startup.sh @@ -2,40 +2,40 @@ # Script to set up automatic Thorium browser launch with Fitatu website on startup # Opens https://www.fitatu.com/ in Thorium browser every time the system boots -set -e # Exit on any error +set -e # Exit on any error # Default to non-interactive mode INTERACTIVE_MODE=false # Parse command line arguments while [[ $# -gt 0 ]]; do - case $1 in - -i|--interactive) - INTERACTIVE_MODE=true - shift - ;; - -h|--help) - echo "Usage: $0 [OPTIONS]" - echo "Options:" - echo " -i, --interactive Enable interactive prompts (default: auto-yes)" - echo " -h, --help Show this help message" - exit 0 - ;; - *) - echo "Unknown option: $1" - echo "Use -h or --help for usage information" - exit 1 - ;; - esac + case $1 in + -i | --interactive) + INTERACTIVE_MODE=true + shift + ;; + -h | --help) + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " -i, --interactive Enable interactive prompts (default: auto-yes)" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use -h or --help for usage information" + exit 1 + ;; + esac done # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to create systemd services." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to create systemd services." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Check for sudo privileges after argument parsing @@ -46,10 +46,10 @@ echo "==================================" echo "Current Date: $(date)" echo "User: $USER" echo "Original user: ${SUDO_USER:-$USER}" -if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" +if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Mode: Interactive (prompts enabled)" else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" + echo "Mode: Automatic (auto-yes, use --interactive for prompts)" fi # Target URL @@ -64,72 +64,72 @@ echo "User home: $USER_HOME" # Function to check if Thorium browser is installed check_thorium_browser() { - echo "" - echo "1. Checking Thorium Browser Installation..." - echo "==========================================" - - if ! command -v "$BROWSER_COMMAND" &> /dev/null; then - echo "Warning: Thorium browser not found in PATH" - echo "Checking alternative locations..." - - # Check common installation paths - local alt_paths=( - "/opt/thorium/thorium" - "/usr/bin/thorium" - "/usr/local/bin/thorium" - "/opt/thorium-browser/thorium-browser" - "${USER_HOME}/.local/bin/thorium-browser" - ) - - local found=false - for path in "${alt_paths[@]}"; do - if [[ -x "$path" ]]; then - BROWSER_COMMAND="$path" - echo "✓ Found Thorium browser at: $path" - found=true - break - fi - done - - if [[ "$found" != true ]]; then - echo "Error: Thorium browser not found!" - echo "Please install Thorium browser first or ensure it's in your PATH." - echo "" - echo "You can install Thorium browser from:" - echo "https://thorium.rocks/" - echo "" - - local continue_anyway=false - - if [[ "$INTERACTIVE_MODE" == "true" ]]; then - read -p "Continue anyway? The service will be created but may fail to start (y/N): " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - continue_anyway=true - fi - else - echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)" - continue_anyway=true - fi - - if [[ "$continue_anyway" != true ]]; then - exit 1 - fi + echo "" + echo "1. Checking Thorium Browser Installation..." + echo "==========================================" + + if ! command -v "$BROWSER_COMMAND" &> /dev/null; then + echo "Warning: Thorium browser not found in PATH" + echo "Checking alternative locations..." + + # Check common installation paths + local alt_paths=( + "/opt/thorium/thorium" + "/usr/bin/thorium" + "/usr/local/bin/thorium" + "/opt/thorium-browser/thorium-browser" + "${USER_HOME}/.local/bin/thorium-browser" + ) + + local found=false + for path in "${alt_paths[@]}"; do + if [[ -x $path ]]; then + BROWSER_COMMAND="$path" + echo "✓ Found Thorium browser at: $path" + found=true + break + fi + done + + if [[ $found != true ]]; then + echo "Error: Thorium browser not found!" + echo "Please install Thorium browser first or ensure it's in your PATH." + echo "" + echo "You can install Thorium browser from:" + echo "https://thorium.rocks/" + echo "" + + local continue_anyway=false + + if [[ $INTERACTIVE_MODE == "true" ]]; then + read -p "Continue anyway? The service will be created but may fail to start (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + continue_anyway=true fi - else - echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)" + else + echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)" + continue_anyway=true + fi + + if [[ $continue_anyway != true ]]; then + exit 1 + fi fi + else + echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)" + fi } # Function to create the browser launcher script create_launcher_script() { - echo "" - echo "2. Creating Browser Launcher Script..." - echo "=====================================" - - local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh" - - cat > "$launcher_script" << EOF + echo "" + echo "2. Creating Browser Launcher Script..." + echo "=====================================" + + local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh" + + cat > "$launcher_script" << EOF #!/bin/bash # Thorium browser launcher for Fitatu website # Created by setup_thorium_startup.sh on $(date) @@ -203,24 +203,24 @@ else fi EOF - chmod +x "$launcher_script" - echo "✓ Created launcher script: $launcher_script" + chmod +x "$launcher_script" + echo "✓ Created launcher script: $launcher_script" } # Function to create systemd service for user session create_user_systemd_service() { - echo "" - echo "3. Creating User Systemd Service..." - echo "==================================" - - local user_systemd_dir="$USER_HOME/.config/systemd/user" - local service_file="$user_systemd_dir/thorium-fitatu-startup.service" - - # Create user systemd directory - sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir" - - # Create the service file - sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF + echo "" + echo "3. Creating User Systemd Service..." + echo "==================================" + + local user_systemd_dir="$USER_HOME/.config/systemd/user" + local service_file="$user_systemd_dir/thorium-fitatu-startup.service" + + # Create user systemd directory + sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir" + + # Create the service file + sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF [Unit] Description=Launch Thorium Browser with Fitatu on Startup After=graphical-session.target @@ -245,18 +245,18 @@ TimeoutStartSec=120 WantedBy=default.target EOF - echo "✓ Created user systemd service: $service_file" + echo "✓ Created user systemd service: $service_file" } # Function to create system-wide systemd service (alternative approach) create_system_systemd_service() { - echo "" - echo "4. Creating System Systemd Service..." - echo "====================================" - - local service_file="/etc/systemd/system/thorium-fitatu-startup.service" - - cat > "$service_file" << EOF + echo "" + echo "4. Creating System Systemd Service..." + echo "====================================" + + local service_file="/etc/systemd/system/thorium-fitatu-startup.service" + + cat > "$service_file" << EOF [Unit] Description=Launch Thorium Browser with Fitatu on Startup After=multi-user.target network-online.target @@ -283,23 +283,23 @@ TimeoutStartSec=180 WantedBy=multi-user.target EOF - echo "✓ Created system systemd service: $service_file" + echo "✓ Created system systemd service: $service_file" } # Function to create autostart desktop entry (additional method) create_autostart_entry() { - echo "" - echo "5. Creating Autostart Desktop Entry..." - echo "=====================================" - - local autostart_dir="$USER_HOME/.config/autostart" - local desktop_file="$autostart_dir/thorium-fitatu.desktop" - - # Create autostart directory - sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir" - - # Create desktop entry - sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF + echo "" + echo "5. Creating Autostart Desktop Entry..." + echo "=====================================" + + local autostart_dir="$USER_HOME/.config/autostart" + local desktop_file="$autostart_dir/thorium-fitatu.desktop" + + # Create autostart directory + sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir" + + # Create desktop entry + sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF [Desktop Entry] Type=Application Name=Thorium Fitatu Startup @@ -314,45 +314,45 @@ Terminal=false Categories=Network;WebBrowser; EOF - echo "✓ Created autostart desktop entry: $desktop_file" + echo "✓ Created autostart desktop entry: $desktop_file" } # Function to create i3 config autostart entry create_i3_autostart() { - echo "" - echo "6. Creating i3 Config Autostart Entry..." - echo "=======================================" - - local i3_config="$USER_HOME/.config/i3/config" - local i3_config_dir="$USER_HOME/.config/i3" - - # Create i3 config directory if it doesn't exist - sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir" - - # Check if i3 config exists - if [[ -f "$i3_config" ]]; then - # Check if autostart entry already exists - if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then - # Add autostart entry to i3 config - sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'" - sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'" - sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'" - echo "✓ Added autostart entry to i3 config: $i3_config" - else - echo "✓ Autostart entry already exists in i3 config" - fi + echo "" + echo "6. Creating i3 Config Autostart Entry..." + echo "=======================================" + + local i3_config="$USER_HOME/.config/i3/config" + local i3_config_dir="$USER_HOME/.config/i3" + + # Create i3 config directory if it doesn't exist + sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir" + + # Check if i3 config exists + if [[ -f $i3_config ]]; then + # Check if autostart entry already exists + if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then + # Add autostart entry to i3 config + sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'" + sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'" + sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'" + echo "✓ Added autostart entry to i3 config: $i3_config" else - echo "Warning: i3 config file not found at $i3_config" - echo "You may need to manually add the following line to your i3 config:" - echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh" + echo "✓ Autostart entry already exists in i3 config" fi + else + echo "Warning: i3 config file not found at $i3_config" + echo "You may need to manually add the following line to your i3 config:" + echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh" + fi } # Function to create a script to enable user service after login create_user_enable_script() { - local enable_script="$USER_HOME/.config/thorium-enable-service.sh" - - sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF' + local enable_script="$USER_HOME/.config/thorium-enable-service.sh" + + sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF' #!/bin/bash # Script to enable thorium-fitatu-startup user service # This runs once to enable the service, then removes itself @@ -365,110 +365,110 @@ systemctl --user enable thorium-fitatu-startup.service rm "$0" EOF - sudo -u "${SUDO_USER}" chmod +x "$enable_script" - - # Add to user's .bashrc to run on next login - local bashrc="$USER_HOME/.bashrc" - if [[ -f "$bashrc" ]]; then - sudo -u "${SUDO_USER}" bash -c "echo '' >> '$bashrc'" - sudo -u "${SUDO_USER}" bash -c "echo '# Auto-enable thorium service (temporary)' >> '$bashrc'" - sudo -u "${SUDO_USER}" bash -c "echo '[[ -x ~/.config/thorium-enable-service.sh ]] && ~/.config/thorium-enable-service.sh' >> '$bashrc'" - fi + sudo -u "${SUDO_USER}" chmod +x "$enable_script" + + # Add to user's .bashrc to run on next login + local bashrc="$USER_HOME/.bashrc" + if [[ -f $bashrc ]]; then + sudo -u "${SUDO_USER}" bash -c "echo '' >> '$bashrc'" + sudo -u "${SUDO_USER}" bash -c "echo '# Auto-enable thorium service (temporary)' >> '$bashrc'" + sudo -u "${SUDO_USER}" bash -c "echo '[[ -x ~/.config/thorium-enable-service.sh ]] && ~/.config/thorium-enable-service.sh' >> '$bashrc'" + fi } # Function to enable services enable_services() { - echo "" - echo "7. Enabling Services..." - echo "======================" - - # Reload systemd daemon - systemctl daemon-reload - echo "✓ System daemon reloaded" - - # Enable system service - systemctl enable thorium-fitatu-startup.service - echo "✓ System service enabled" - - # Enable lingering for the user (allows user services to run without login) - loginctl enable-linger "${SUDO_USER}" - echo "✓ User lingering enabled" - - # Create a script to enable user service after login - create_user_enable_script - echo "✓ User service will be enabled on next login" + echo "" + echo "7. Enabling Services..." + echo "======================" + + # Reload systemd daemon + systemctl daemon-reload + echo "✓ System daemon reloaded" + + # Enable system service + systemctl enable thorium-fitatu-startup.service + echo "✓ System service enabled" + + # Enable lingering for the user (allows user services to run without login) + loginctl enable-linger "${SUDO_USER}" + echo "✓ User lingering enabled" + + # Create a script to enable user service after login + create_user_enable_script + echo "✓ User service will be enabled on next login" } # Function to test the setup test_setup() { - echo "" - echo "8. Testing Setup..." - echo "==================" - - local run_test=true - - if [[ "$INTERACTIVE_MODE" == "true" ]]; then - echo "Would you like to test the browser launcher now?" - read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r - echo - - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - run_test=false - fi - else - echo "Auto-testing the browser launcher (use --interactive to prompt)" + echo "" + echo "8. Testing Setup..." + echo "==================" + + local run_test=true + + if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Would you like to test the browser launcher now?" + read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r + echo + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + run_test=false fi - - if [[ "$run_test" == "true" ]]; then - echo "Testing browser launch..." - echo "Note: This will open Thorium browser with Fitatu website" - - # Test the launcher immediately - if /usr/local/bin/thorium-fitatu-launcher.sh; then - echo "✓ Test launch completed successfully" - else - echo "✗ Test launch failed" - echo "Check that Thorium browser is properly installed and accessible" - fi + else + echo "Auto-testing the browser launcher (use --interactive to prompt)" + fi + + if [[ $run_test == "true" ]]; then + echo "Testing browser launch..." + echo "Note: This will open Thorium browser with Fitatu website" + + # Test the launcher immediately + if /usr/local/bin/thorium-fitatu-launcher.sh; then + echo "✓ Test launch completed successfully" else - echo "Skipping test launch" + echo "✗ Test launch failed" + echo "Check that Thorium browser is properly installed and accessible" fi + else + echo "Skipping test launch" + fi } # Function to show usage instructions show_instructions() { - echo "" - echo "==========================================" - echo "Thorium Browser Auto-Startup Setup Complete" - echo "==========================================" - echo "Summary:" - echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh" - echo "✓ System service created: thorium-fitatu-startup.service" - echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service" - echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop" - echo "✓ i3 autostart entry added to: ~/.config/i3/config" - echo "✓ Services enabled for automatic startup" - echo "" - echo "The system will now:" - echo "• Launch Thorium browser with $TARGET_URL on every startup" - echo "• Use multiple methods to ensure reliable startup" - echo "• Wait for desktop environment to be ready before launching" - echo "• User service will be enabled automatically on next login" - echo "" - echo "To check status:" - echo " systemctl status thorium-fitatu-startup.service" - echo " systemctl --user status thorium-fitatu-startup.service (after login)" - echo "" - echo "To view logs:" - echo " journalctl -u thorium-fitatu-startup.service" - echo " journalctl --user -u thorium-fitatu-startup.service" - echo "" - echo "To disable (if needed):" - echo " sudo systemctl disable thorium-fitatu-startup.service" - echo " systemctl --user disable thorium-fitatu-startup.service" - echo " rm ~/.config/autostart/thorium-fitatu.desktop" - echo "" - echo "IMPORTANT: Browser will launch automatically on next reboot!" + echo "" + echo "==========================================" + echo "Thorium Browser Auto-Startup Setup Complete" + echo "==========================================" + echo "Summary:" + echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh" + echo "✓ System service created: thorium-fitatu-startup.service" + echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service" + echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop" + echo "✓ i3 autostart entry added to: ~/.config/i3/config" + echo "✓ Services enabled for automatic startup" + echo "" + echo "The system will now:" + echo "• Launch Thorium browser with $TARGET_URL on every startup" + echo "• Use multiple methods to ensure reliable startup" + echo "• Wait for desktop environment to be ready before launching" + echo "• User service will be enabled automatically on next login" + echo "" + echo "To check status:" + echo " systemctl status thorium-fitatu-startup.service" + echo " systemctl --user status thorium-fitatu-startup.service (after login)" + echo "" + echo "To view logs:" + echo " journalctl -u thorium-fitatu-startup.service" + echo " journalctl --user -u thorium-fitatu-startup.service" + echo "" + echo "To disable (if needed):" + echo " sudo systemctl disable thorium-fitatu-startup.service" + echo " systemctl --user disable thorium-fitatu-startup.service" + echo " rm ~/.config/autostart/thorium-fitatu.desktop" + echo "" + echo "IMPORTANT: Browser will launch automatically on next reboot!" } # Main execution diff --git a/scripts/system-maintenance/bin/browser-preexec-wrapper.sh b/scripts/system-maintenance/bin/browser-preexec-wrapper.sh index 3e4fec8..bc17de7 100755 --- a/scripts/system-maintenance/bin/browser-preexec-wrapper.sh +++ b/scripts/system-maintenance/bin/browser-preexec-wrapper.sh @@ -11,7 +11,7 @@ real_bin="/usr/bin/${prog_name}" # If run directly (not via a browser symlink) or if the target binary doesn't exist, # allow passing the real browser command as the first argument for testing: -if [[ ! -x "$real_bin" || "$prog_name" == "browser-preexec-wrapper.sh" ]]; then +if [[ ! -x $real_bin || $prog_name == "browser-preexec-wrapper.sh" ]]; then if [[ $# -ge 1 ]]; then real_bin="$1" shift @@ -24,10 +24,10 @@ if [[ ! -x "$real_bin" || "$prog_name" == "browser-preexec-wrapper.sh" ]]; then fi # Best-effort: install hosts file quietly; don't block browser startup -if command -v sudo >/dev/null 2>&1; then - sudo -n "$HOSTS_INSTALL_SCRIPT" >/dev/null 2>&1 || true +if command -v sudo > /dev/null 2>&1; then + sudo -n "$HOSTS_INSTALL_SCRIPT" > /dev/null 2>&1 || true else - "$HOSTS_INSTALL_SCRIPT" >/dev/null 2>&1 || true + "$HOSTS_INSTALL_SCRIPT" > /dev/null 2>&1 || true fi exec "$real_bin" "$@" diff --git a/scripts/system-maintenance/bin/hosts-file-monitor.sh b/scripts/system-maintenance/bin/hosts-file-monitor.sh index 4cf3255..b4d9e0d 100755 --- a/scripts/system-maintenance/bin/hosts-file-monitor.sh +++ b/scripts/system-maintenance/bin/hosts-file-monitor.sh @@ -11,100 +11,100 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__" # Function to log with timestamp log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2 + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2 } # Function to check if hosts file needs restoration needs_restoration() { - # Check if file exists - if [[ ! -f "$HOSTS_FILE" ]]; then - return 0 # File missing, needs restoration - fi - - # Check if file is empty or too small (less than 1000 lines indicates tampering) - local line_count - line_count=$(wc -l < "$HOSTS_FILE" 2>/dev/null || echo "0") - if [[ "$line_count" -lt 1000 ]]; then - return 0 # File too small, likely tampered with - fi - - # Check if our custom entries are missing - if ! grep -q "Custom blocking entries" "$HOSTS_FILE" 2>/dev/null; then - return 0 # Our custom entries missing, needs restoration - fi - - # Check if StevenBlack entries are missing - if ! grep -q "StevenBlack" "$HOSTS_FILE" 2>/dev/null; then - return 0 # StevenBlack entries missing, needs restoration - fi - - return 1 # File seems intact + # Check if file exists + if [[ ! -f $HOSTS_FILE ]]; then + return 0 # File missing, needs restoration + fi + + # Check if file is empty or too small (less than 1000 lines indicates tampering) + local line_count + line_count=$(wc -l < "$HOSTS_FILE" 2> /dev/null || echo "0") + if [[ $line_count -lt 1000 ]]; then + return 0 # File too small, likely tampered with + fi + + # Check if our custom entries are missing + if ! grep -q "Custom blocking entries" "$HOSTS_FILE" 2> /dev/null; then + return 0 # Our custom entries missing, needs restoration + fi + + # Check if StevenBlack entries are missing + if ! grep -q "StevenBlack" "$HOSTS_FILE" 2> /dev/null; then + return 0 # StevenBlack entries missing, needs restoration + fi + + return 1 # File seems intact } # Function to restore hosts file restore_hosts_file() { - log_message "Hosts file modification detected - initiating restoration" - - if [[ -f "$HOSTS_INSTALL_SCRIPT" ]]; then - log_message "Running hosts installation script: $HOSTS_INSTALL_SCRIPT" - - if bash "$HOSTS_INSTALL_SCRIPT" >> "$LOG_FILE" 2>&1; then - log_message "Hosts file restoration completed successfully" - else - log_message "Hosts file restoration failed with exit code $?" - fi + log_message "Hosts file modification detected - initiating restoration" + + if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then + log_message "Running hosts installation script: $HOSTS_INSTALL_SCRIPT" + + if bash "$HOSTS_INSTALL_SCRIPT" >> "$LOG_FILE" 2>&1; then + log_message "Hosts file restoration completed successfully" else - log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT" + log_message "Hosts file restoration failed with exit code $?" fi + else + log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT" + fi } # Function to monitor with inotifywait monitor_with_inotify() { - log_message "Starting hosts file monitoring with inotify" - - # Monitor the hosts file and its directory for various events - inotifywait -m -e delete,move,modify,attrib,create --format '%w%f %e %T' --timefmt '%Y-%m-%d %H:%M:%S' "$HOSTS_FILE" /etc/ 2>/dev/null | \ + log_message "Starting hosts file monitoring with inotify" + + # Monitor the hosts file and its directory for various events + inotifywait -m -e delete,move,modify,attrib,create --format '%w%f %e %T' --timefmt '%Y-%m-%d %H:%M:%S' "$HOSTS_FILE" /etc/ 2> /dev/null | while read -r file event time; do - # Check if the event is related to our hosts file - if [[ "$file" == "$HOSTS_FILE" ]] || [[ "$file" == "/etc/hosts" ]]; then - log_message "Event detected: $event on $file at $time" - - # Small delay to avoid rapid-fire events - sleep 2 - - # Check if restoration is needed - if needs_restoration; then - restore_hosts_file - else - log_message "Hosts file check passed - no restoration needed" - fi + # Check if the event is related to our hosts file + if [[ $file == "$HOSTS_FILE" ]] || [[ $file == "/etc/hosts" ]]; then + log_message "Event detected: $event on $file at $time" + + # Small delay to avoid rapid-fire events + sleep 2 + + # Check if restoration is needed + if needs_restoration; then + restore_hosts_file + else + log_message "Hosts file check passed - no restoration needed" fi + fi done } # Function to monitor with polling (fallback) monitor_with_polling() { - log_message "Starting hosts file monitoring with polling (fallback method)" - - while true; do - if needs_restoration; then - restore_hosts_file - fi - - # Check every 30 seconds - sleep 30 - done + log_message "Starting hosts file monitoring with polling (fallback method)" + + while true; do + if needs_restoration; then + restore_hosts_file + fi + + # Check every 30 seconds + sleep 30 + done } # Main execution log_message "=== Hosts File Monitor Started ===" # Check if inotify-tools is available -if command -v inotifywait >/dev/null 2>&1; then - log_message "Using inotify for file monitoring" - monitor_with_inotify +if command -v inotifywait > /dev/null 2>&1; then + log_message "Using inotify for file monitoring" + monitor_with_inotify else - log_message "inotify-tools not available, using polling method" - log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools" - monitor_with_polling + log_message "inotify-tools not available, using polling method" + log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools" + monitor_with_polling fi diff --git a/scripts/system-maintenance/bin/periodic-system-maintenance.sh b/scripts/system-maintenance/bin/periodic-system-maintenance.sh index cd80264..2127daf 100755 --- a/scripts/system-maintenance/bin/periodic-system-maintenance.sh +++ b/scripts/system-maintenance/bin/periodic-system-maintenance.sh @@ -13,30 +13,30 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__" # Function to log with timestamp log_message() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" + echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" } # Function to execute with logging execute_with_log() { - local script_path="$1" - local script_name="$2" - - log_message "Starting $script_name" - echo "Executing $script_name..." >&2 - - if [[ -f "$script_path" ]]; then - if bash "$script_path" >> "$LOG_FILE" 2>&1; then - log_message "$script_name completed successfully" - echo "✓ $script_name completed successfully" >&2 - else - local ec=$? - log_message "$script_name failed with exit code $ec" - echo "✗ $script_name failed (exit $ec)" >&2 - fi + local script_path="$1" + local script_name="$2" + + log_message "Starting $script_name" + echo "Executing $script_name..." >&2 + + if [[ -f $script_path ]]; then + if bash "$script_path" >> "$LOG_FILE" 2>&1; then + log_message "$script_name completed successfully" + echo "✓ $script_name completed successfully" >&2 else - log_message "$script_name not found at $script_path" - echo "✗ $script_name not found at $script_path" >&2 + local ec=$? + log_message "$script_name failed with exit code $ec" + echo "✗ $script_name failed (exit $ec)" >&2 fi + else + log_message "$script_name not found at $script_path" + echo "✗ $script_name not found at $script_path" >&2 + fi } # Start maintenance diff --git a/scripts/test_removal.sh b/scripts/test_removal.sh index 31a3903..58e7adc 100644 --- a/scripts/test_removal.sh +++ b/scripts/test_removal.sh @@ -5,38 +5,37 @@ set -euo pipefail DOWNLOADS_DIR="$HOME/Downloads" -HOME_DIR="$HOME" # Test function test_file_removal() { - local files=() - - # Find a few test files - while IFS= read -r -d '' file; do - files+=("$file") - done < <(find "$DOWNLOADS_DIR" -name "*.jpg" -print0 2>/dev/null | head -z -n 2) - - echo "Found ${#files[@]} test files:" - for file in "${files[@]}"; do - echo " - $file" - done - - echo "Attempting to remove files..." - local removed=0 - local failed=0 - - for file in "${files[@]}"; do - echo "Removing: $file" - if rm "$file" 2>/dev/null; then - echo " SUCCESS" - ((removed++)) - else - echo " FAILED (exit code: $?)" - ((failed++)) - fi - done - - echo "Results: $removed removed, $failed failed" + local files=() + + # Find a few test files + while IFS= read -r -d '' file; do + files+=("$file") + done < <(find "$DOWNLOADS_DIR" -name "*.jpg" -print0 2> /dev/null | head -z -n 2) + + echo "Found ${#files[@]} test files:" + for file in "${files[@]}"; do + echo " - $file" + done + + echo "Attempting to remove files..." + local removed=0 + local failed=0 + + for file in "${files[@]}"; do + echo "Removing: $file" + if rm "$file" 2> /dev/null; then + echo " SUCCESS" + ((removed++)) + else + echo " FAILED (exit code: $?)" + ((failed++)) + fi + done + + echo "Results: $removed removed, $failed failed" } test_file_removal diff --git a/scripts/utils/convert_words.sh b/scripts/utils/convert_words.sh index b2849d5..0115a2c 100755 --- a/scripts/utils/convert_words.sh +++ b/scripts/utils/convert_words.sh @@ -2,25 +2,25 @@ # Check if input and output files are provided if [ $# -ne 2 ]; then - echo "Usage: $0 input_file.txt output_file.txt" - exit 1 + echo "Usage: $0 input_file.txt output_file.txt" + exit 1 fi # Check if the input file exists if [ ! -f "$1" ]; then - echo "Error: File '$1' not found" - exit 1 + echo "Error: File '$1' not found" + exit 1 fi # Store output file name output_file="$2" # Clear output file at the beginning -> "$output_file" +: > "$output_file" # Process file using a pipeline of specialized tools # 1. tr - remove non-alphabetic chars except newlines -# 2. tr - convert to uppercase +# 2. tr - convert to uppercase # 3. grep - filter by length (5-8 characters) # 4. sort - sort the words alphabetically # 5. uniq - remove duplicates diff --git a/scripts/utils/organize_downloads.sh b/scripts/utils/organize_downloads.sh index f0dce47..c3e2c92 100755 --- a/scripts/utils/organize_downloads.sh +++ b/scripts/utils/organize_downloads.sh @@ -13,7 +13,7 @@ SAMPLE_LIMIT=20 # Simple usage helper usage() { - cat <&2 - usage - exit 1 - ;; - esac + case "${1}" in + -n | --dry-run) + DRY_RUN=true + shift + ;; + --sample=*) + SAMPLE_LIMIT="${1#*=}" + shift + ;; + -h | --help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac done # Function to check if file has media extension is_media_file() { - local file="$1" - local extension="${file##*.}" - extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]') - - # Check if it's an image - for ext in "${IMAGE_EXTENSIONS[@]}"; do - if [[ "$extension" == "$ext" ]]; then - return 0 - fi - done - - # Check if it's a video - for ext in "${VIDEO_EXTENSIONS[@]}"; do - if [[ "$extension" == "$ext" ]]; then - return 0 - fi - done - - return 1 + local file="$1" + local extension="${file##*.}" + extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]') + + # Check if it's an image + for ext in "${IMAGE_EXTENSIONS[@]}"; do + if [[ $extension == "$ext" ]]; then + return 0 + fi + done + + # Check if it's a video + for ext in "${VIDEO_EXTENSIONS[@]}"; do + if [[ $extension == "$ext" ]]; then + return 0 + fi + done + + return 1 } # Function to find media files in a directory (non-recursive for home, avoid common system dirs) find_media_files() { - local search_dir="$1" - local files=() - # Directories to exclude under Downloads - local -a EXCLUDES=( - ".git" ".hg" ".svn" ".cache" "node_modules" "dist" "build" "out" "target" "coverage" "__pycache__" "venv" ".venv" - # previous staging dirs created by this script - ".media_organize_" "media_organize_" - ) - - if [[ "$search_dir" == "$HOME_DIR" ]]; then - # For home directory, only check files directly in ~ (not subdirectories) - # Exclude common system/config directories - while IFS= read -r -d '' file; do - local basename=$(basename "$file") - # Skip hidden files and common system directories - if [[ ! "$basename" =~ ^\. ]] && [[ -f "$file" ]]; then - if is_media_file "$file"; then - files+=("$file") - fi - fi - done < <(find "$search_dir" -maxdepth 1 -type f -print0 2>/dev/null) - else - # For Downloads, search recursively, pruning excluded directories - # Build prune expression - local prune_expr=() - for ex in "${EXCLUDES[@]}"; do - prune_expr+=( -name "$ex*" -o ) - done - # Remove trailing -o - unset 'prune_expr[${#prune_expr[@]}-1]' + local search_dir="$1" + local files=() + # Directories to exclude under Downloads + local -a EXCLUDES=( + ".git" ".hg" ".svn" ".cache" "node_modules" "dist" "build" "out" "target" "coverage" "__pycache__" "venv" ".venv" + # previous staging dirs created by this script + ".media_organize_" "media_organize_" + ) - while IFS= read -r -d '' file; do - if is_media_file "$file"; then - files+=("$file") - fi - done < <(find "$search_dir" \( -type d \( ${prune_expr[@]} \) -prune \) -o -type f -print0 2>/dev/null) - fi - - printf '%s\n' "${files[@]}" + if [[ $search_dir == "$HOME_DIR" ]]; then + # For home directory, only check files directly in ~ (not subdirectories) + # Exclude common system/config directories + while IFS= read -r -d '' file; do + local basename + basename=$(basename "$file") + # Skip hidden files and common system directories + if [[ ! $basename =~ ^\. ]] && [[ -f $file ]]; then + if is_media_file "$file"; then + files+=("$file") + fi + fi + done < <(find "$search_dir" -maxdepth 1 -type f -print0 2> /dev/null) + else + # For Downloads, search recursively, pruning excluded directories + # Build prune expression + local prune_expr=() + for ex in "${EXCLUDES[@]}"; do + prune_expr+=(-name "$ex*" -o) + done + # Remove trailing -o + unset 'prune_expr[${#prune_expr[@]}-1]' + + while IFS= read -r -d '' file; do + if is_media_file "$file"; then + files+=("$file") + fi + done < <(find "$search_dir" \( -type d \( "${prune_expr[@]}" \) -prune \) -o -type f -print0 2> /dev/null) + fi + + printf '%s\n' "${files[@]}" } # Function to create timestamped zip archive create_media_archive() { - local files=("$@") - - if [[ ${#files[@]} -eq 0 ]]; then - log "No media files found to archive." - return 0 - fi - - # Create timestamp for archive name - local timestamp=$(date '+%Y%m%d_%H%M%S') - local archive_name="media_archive_${timestamp}.zip" - local archive_path="$DOWNLOADS_DIR/$archive_name" - - # Create temporary directory (fallback to /tmp if needed) - if ! mkdir -p "$TEMP_DIR" 2>/dev/null; then - TEMP_DIR="/tmp/media_organize_$$" - mkdir -p "$TEMP_DIR" - fi + local files=("$@") - # Ensure temp dir is cleaned up on function return; trap unsets itself after running - trap 'rm -rf "$TEMP_DIR" 2>/dev/null || true; trap - RETURN' RETURN - - log "Found ${#files[@]} media files to archive." - log "Creating archive: $archive_path" - - # Copy files to temp directory maintaining relative structure - local successfully_copied=() - local copy_errors=0 - - for file in "${files[@]}"; do - if [[ -f "$file" ]]; then - local relative_path="" - if [[ "$file" == "$DOWNLOADS_DIR"* ]]; then - relative_path="downloads/${file#$DOWNLOADS_DIR/}" - else - relative_path="home/${file#$HOME_DIR/}" - fi - - local temp_file="$TEMP_DIR/$relative_path" - local temp_dir=$(dirname "$temp_file") - - mkdir -p "$temp_dir" - # Check readability first to provide a clearer error - if [[ ! -r "$file" ]]; then - log "WARNING: Cannot read $file (permission denied?)" - ((copy_errors++)) - continue - fi + if [[ ${#files[@]} -eq 0 ]]; then + log "No media files found to archive." + return 0 + fi - # Attempt copy and capture any error for logging - local cp_err - if cp_err=$(cp -p "$file" "$temp_file" 2>&1); then - successfully_copied+=("$file") - else - # Surface the cp error so the user can see the reason - log "WARNING: Failed to copy $file -> $cp_err" - # Special hint for space issues - if echo "$cp_err" | grep -qi "No space left on device"; then - log "HINT: Not enough free space to stage files. Using $TEMP_DIR. Free up space or change TEMP_DIR." - fi - ((copy_errors++)) - fi + # Create timestamp for archive name + local timestamp + timestamp=$(date '+%Y%m%d_%H%M%S') + local archive_name="media_archive_${timestamp}.zip" + local archive_path="$DOWNLOADS_DIR/$archive_name" + + # Create temporary directory (fallback to /tmp if needed) + if ! mkdir -p "$TEMP_DIR" 2> /dev/null; then + TEMP_DIR="/tmp/media_organize_$$" + mkdir -p "$TEMP_DIR" + fi + + # Ensure temp dir is cleaned up on function return; trap unsets itself after running + trap 'rm -rf "$TEMP_DIR" 2>/dev/null || true; trap - RETURN' RETURN + + log "Found ${#files[@]} media files to archive." + log "Creating archive: $archive_path" + + # Copy files to temp directory maintaining relative structure + local successfully_copied=() + local copy_errors=0 + + for file in "${files[@]}"; do + if [[ -f $file ]]; then + local relative_path="" + if [[ $file == "$DOWNLOADS_DIR"* ]]; then + relative_path="downloads/${file#"$DOWNLOADS_DIR"/}" + else + relative_path="home/${file#"$HOME_DIR"/}" + fi + + local temp_file="$TEMP_DIR/$relative_path" + local temp_dir + temp_dir=$(dirname "$temp_file") + + mkdir -p "$temp_dir" + # Check readability first to provide a clearer error + if [[ ! -r $file ]]; then + log "WARNING: Cannot read $file (permission denied?)" + ((copy_errors++)) + continue + fi + + # Attempt copy and capture any error for logging + local cp_err + if cp_err=$(cp -p "$file" "$temp_file" 2>&1); then + successfully_copied+=("$file") + else + # Surface the cp error so the user can see the reason + log "WARNING: Failed to copy $file -> $cp_err" + # Special hint for space issues + if echo "$cp_err" | grep -qi "No space left on device"; then + log "HINT: Not enough free space to stage files. Using $TEMP_DIR. Free up space or change TEMP_DIR." fi + ((copy_errors++)) + fi + fi + done + + if [[ ${#successfully_copied[@]} -eq 0 ]]; then + log "ERROR: No files were successfully copied to temp directory." + rm -rf "$TEMP_DIR" + return 1 + fi + + if [[ $copy_errors -gt 0 ]]; then + log "WARNING: $copy_errors files failed to copy." + fi + + # Create zip archive with maximum compression + log "Creating zip archive with ${#successfully_copied[@]} files..." + cd "$TEMP_DIR" + if zip -9 -r "$archive_path" . 2>&1; then + log "Successfully created archive with ${#successfully_copied[@]} files." + + # Verify the zip file was actually created and is not empty + if [[ ! -f $archive_path ]]; then + log "ERROR: Archive file was not created at $archive_path" + rm -rf "$TEMP_DIR" + return 1 + fi + + local archive_size + archive_size=$(stat -c%s "$archive_path" 2> /dev/null || echo "0") + if [[ $archive_size -eq 0 ]]; then + log "ERROR: Archive file is empty" + rm -rf "$TEMP_DIR" + return 1 + fi + + # Remove original files only if zip was successful + local removed_count=0 + local remove_errors=0 + + log "Starting to remove ${#successfully_copied[@]} original files..." + + # Temporarily disable strict error handling for file removal + set +e + + for file in "${successfully_copied[@]}"; do + if [[ -f $file ]]; then + if rm "$file" 2> /dev/null; then + removed_count=$((removed_count + 1)) + log "Removed: $(basename "$file")" + else + remove_errors=$((remove_errors + 1)) + log "ERROR: Failed to remove $(basename "$file")" + fi + else + log "WARNING: File no longer exists: $(basename "$file")" + fi done - - if [[ ${#successfully_copied[@]} -eq 0 ]]; then - log "ERROR: No files were successfully copied to temp directory." - rm -rf "$TEMP_DIR" - return 1 + + # Re-enable strict error handling + set -e + + log "Successfully removed $removed_count original files." + if [[ $remove_errors -gt 0 ]]; then + log "WARNING: Failed to remove $remove_errors files." fi - - if [[ $copy_errors -gt 0 ]]; then - log "WARNING: $copy_errors files failed to copy." - fi - - # Create zip archive with maximum compression - log "Creating zip archive with ${#successfully_copied[@]} files..." - cd "$TEMP_DIR" - if zip -9 -r "$archive_path" . 2>&1; then - log "Successfully created archive with ${#successfully_copied[@]} files." - - # Verify the zip file was actually created and is not empty - if [[ ! -f "$archive_path" ]]; then - log "ERROR: Archive file was not created at $archive_path" - rm -rf "$TEMP_DIR" - return 1 - fi - - local archive_size=$(stat -c%s "$archive_path" 2>/dev/null || echo "0") - if [[ "$archive_size" -eq 0 ]]; then - log "ERROR: Archive file is empty" - rm -rf "$TEMP_DIR" - return 1 - fi - - # Remove original files only if zip was successful - local removed_count=0 - local remove_errors=0 - - log "Starting to remove ${#successfully_copied[@]} original files..." - - # Temporarily disable strict error handling for file removal - set +e - - for file in "${successfully_copied[@]}"; do - if [[ -f "$file" ]]; then - if rm "$file" 2>/dev/null; then - removed_count=$((removed_count + 1)) - log "Removed: $(basename "$file")" - else - remove_errors=$((remove_errors + 1)) - log "ERROR: Failed to remove $(basename "$file")" - fi - else - log "WARNING: File no longer exists: $(basename "$file")" - fi - done - - # Re-enable strict error handling - set -e - - log "Successfully removed $removed_count original files." - if [[ $remove_errors -gt 0 ]]; then - log "WARNING: Failed to remove $remove_errors files." - fi - log "Archive size: $(du -h "$archive_path" | cut -f1)" - + log "Archive size: $(du -h "$archive_path" | cut -f1)" + # Cleanup temp directory (trap will also attempt, which is safe) rm -rf "$TEMP_DIR" - - # Return success only if we removed files or there were no files to remove - if [[ $removed_count -gt 0 ]] || [[ ${#successfully_copied[@]} -eq 0 ]]; then - return 0 - else - log "ERROR: Failed to remove any files after successful archive creation." - return 1 - fi + + # Return success only if we removed files or there were no files to remove + if [[ $removed_count -gt 0 ]] || [[ ${#successfully_copied[@]} -eq 0 ]]; then + return 0 else - log "ERROR: Failed to create archive. Original files preserved." - log "Zip command failed." - rm -rf "$TEMP_DIR" - return 1 + log "ERROR: Failed to remove any files after successful archive creation." + return 1 fi + else + log "ERROR: Failed to create archive. Original files preserved." + log "Zip command failed." + rm -rf "$TEMP_DIR" + return 1 + fi } # Main execution main() { - log "Starting media file organization..." - - # Check if required directories exist - if [[ ! -d "$DOWNLOADS_DIR" ]]; then - log "ERROR: Downloads directory not found: $DOWNLOADS_DIR" - exit 1 - fi - - if [[ ! -d "$HOME_DIR" ]]; then - log "ERROR: Home directory not found: $HOME_DIR" - exit 1 - fi - - # Check if zip command is available - if ! command -v zip >/dev/null 2>&1; then - log "ERROR: zip command not found. Please install zip package." - exit 1 - fi - - # Find all media files - log "Scanning for media files..." - local all_files=() - - # Find files in Downloads directory - log "Scanning Downloads directory..." - while IFS= read -r file; do - [[ -n "$file" ]] && all_files+=("$file") - done < <(find_media_files "$DOWNLOADS_DIR") - - # Find files in home directory (only direct files, not subdirectories) - log "Scanning home directory (root level only)..." - while IFS= read -r file; do - [[ -n "$file" ]] && all_files+=("$file") - done < <(find_media_files "$HOME_DIR") - - if $DRY_RUN; then - log "Dry-run mode: summarizing what would be archived." - if [[ ${#all_files[@]} -eq 0 ]]; then - log "No media files found to organize." - exit 0 - fi + log "Starting media file organization..." - # Count by extension - declare -A ext_counts=() - # Count by top-level directory under Downloads - declare -A dir_counts=() - # Sample paths for suspect extensions - declare -A samples_ts=() - declare -A samples_svg=() - declare -A samples_ico=() + # Check if required directories exist + if [[ ! -d $DOWNLOADS_DIR ]]; then + log "ERROR: Downloads directory not found: $DOWNLOADS_DIR" + exit 1 + fi - for f in "${all_files[@]}"; do - # Extension - ext="${f##*.}" - ext="${ext,,}" - ((ext_counts["$ext"]++)) || true + if [[ ! -d $HOME_DIR ]]; then + log "ERROR: Home directory not found: $HOME_DIR" + exit 1 + fi - # Top directory under Downloads - if [[ "$f" == "$DOWNLOADS_DIR"/* ]]; then - rel="${f#"$DOWNLOADS_DIR"/}" - topdir="${rel%%/*}" - [[ "$topdir" == "$rel" ]] && topdir="." - ((dir_counts["$topdir"]++)) || true - else - ((dir_counts["~"]++)) || true - fi + # Check if zip command is available + if ! command -v zip > /dev/null 2>&1; then + log "ERROR: zip command not found. Please install zip package." + exit 1 + fi - # Samples for suspect extensions - case "$ext" in - ts) - if [[ ${#samples_ts[@]} -lt $SAMPLE_LIMIT ]]; then samples_ts["$f"]=1; fi - ;; - svg) - if [[ ${#samples_svg[@]} -lt $SAMPLE_LIMIT ]]; then samples_svg["$f"]=1; fi - ;; - ico) - if [[ ${#samples_ico[@]} -lt $SAMPLE_LIMIT ]]; then samples_ico["$f"]=1; fi - ;; - esac - done + # Find all media files + log "Scanning for media files..." + local all_files=() - echo "" - echo "Summary by extension (top 20):" - for k in "${!ext_counts[@]}"; do - printf "%8d %s\n" "${ext_counts[$k]}" "$k" - done | sort -nr | head -n 20 + # Find files in Downloads directory + log "Scanning Downloads directory..." + while IFS= read -r file; do + [[ -n $file ]] && all_files+=("$file") + done < <(find_media_files "$DOWNLOADS_DIR") - echo "" - echo "Top contributing directories under Downloads (top 20):" - for k in "${!dir_counts[@]}"; do - printf "%8d %s\n" "${dir_counts[$k]}" "$k" - done | sort -nr | head -n 20 + # Find files in home directory (only direct files, not subdirectories) + log "Scanning home directory (root level only)..." + while IFS= read -r file; do + [[ -n $file ]] && all_files+=("$file") + done < <(find_media_files "$HOME_DIR") - echo "" - if [[ ${#samples_ts[@]} -gt 0 ]]; then - echo "Sample .ts files (TypeScript vs Transport Stream) up to $SAMPLE_LIMIT:" - for p in "${!samples_ts[@]}"; do echo " $p"; done | sort - echo "" - fi - if [[ ${#samples_svg[@]} -gt 0 ]]; then - echo "Sample .svg files up to $SAMPLE_LIMIT:" - for p in "${!samples_svg[@]}"; do echo " $p"; done | sort - echo "" - fi - if [[ ${#samples_ico[@]} -gt 0 ]]; then - echo "Sample .ico files up to $SAMPLE_LIMIT:" - for p in "${!samples_ico[@]}"; do echo " $p"; done | sort - echo "" - fi - - echo "Total files that would be archived: ${#all_files[@]}" - echo "(Use: $(basename "$0") --dry-run --sample=50 to see more examples.)" - exit 0 + if $DRY_RUN; then + log "Dry-run mode: summarizing what would be archived." + if [[ ${#all_files[@]} -eq 0 ]]; then + log "No media files found to organize." + exit 0 fi - # Create archive if files found - if [[ ${#all_files[@]} -gt 0 ]]; then - create_media_archive "${all_files[@]}" - log "Media organization completed successfully." - else - log "No media files found to organize." + # Count by extension + declare -A ext_counts=() + # Count by top-level directory under Downloads + declare -A dir_counts=() + # Sample paths for suspect extensions + declare -A samples_ts=() + declare -A samples_svg=() + declare -A samples_ico=() + + for f in "${all_files[@]}"; do + # Extension + ext="${f##*.}" + ext="${ext,,}" + ((ext_counts["$ext"]++)) || true + + # Top directory under Downloads + if [[ $f == "$DOWNLOADS_DIR"/* ]]; then + rel="${f#"$DOWNLOADS_DIR"/}" + topdir="${rel%%/*}" + [[ $topdir == "$rel" ]] && topdir="." + ((dir_counts["$topdir"]++)) || true + else + ((dir_counts["~"]++)) || true + fi + + # Samples for suspect extensions + case "$ext" in + ts) + if [[ ${#samples_ts[@]} -lt $SAMPLE_LIMIT ]]; then samples_ts["$f"]=1; fi + ;; + svg) + if [[ ${#samples_svg[@]} -lt $SAMPLE_LIMIT ]]; then samples_svg["$f"]=1; fi + ;; + ico) + if [[ ${#samples_ico[@]} -lt $SAMPLE_LIMIT ]]; then samples_ico["$f"]=1; fi + ;; + esac + done + + echo "" + echo "Summary by extension (top 20):" + for k in "${!ext_counts[@]}"; do + printf "%8d %s\n" "${ext_counts[$k]}" "$k" + done | sort -nr | head -n 20 + + echo "" + echo "Top contributing directories under Downloads (top 20):" + for k in "${!dir_counts[@]}"; do + printf "%8d %s\n" "${dir_counts[$k]}" "$k" + done | sort -nr | head -n 20 + + echo "" + if [[ ${#samples_ts[@]} -gt 0 ]]; then + echo "Sample .ts files (TypeScript vs Transport Stream) up to $SAMPLE_LIMIT:" + for p in "${!samples_ts[@]}"; do echo " $p"; done | sort + echo "" fi + if [[ ${#samples_svg[@]} -gt 0 ]]; then + echo "Sample .svg files up to $SAMPLE_LIMIT:" + for p in "${!samples_svg[@]}"; do echo " $p"; done | sort + echo "" + fi + if [[ ${#samples_ico[@]} -gt 0 ]]; then + echo "Sample .ico files up to $SAMPLE_LIMIT:" + for p in "${!samples_ico[@]}"; do echo " $p"; done | sort + echo "" + fi + + echo "Total files that would be archived: ${#all_files[@]}" + echo "(Use: $(basename "$0") --dry-run --sample=50 to see more examples.)" + exit 0 + fi + + # Create archive if files found + if [[ ${#all_files[@]} -gt 0 ]]; then + create_media_archive "${all_files[@]}" + log "Media organization completed successfully." + else + log "No media files found to organize." + fi } # Run main function -main "$@" \ No newline at end of file +main "$@" diff --git a/scripts/utils/setup_media_organizer.sh b/scripts/utils/setup_media_organizer.sh index 9256ace..6b8c149 100755 --- a/scripts/utils/setup_media_organizer.sh +++ b/scripts/utils/setup_media_organizer.sh @@ -13,20 +13,20 @@ USER_NAME="$(whoami)" # Function to log messages log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } # Check if organize script exists -if [[ ! -f "$ORGANIZE_SCRIPT" ]]; then - log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT" - exit 1 +if [[ ! -f $ORGANIZE_SCRIPT ]]; then + log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT" + exit 1 fi # Check if running as root for systemd service creation if [[ $EUID -ne 0 ]]; then - log "This script needs to be run as root to create systemd service." - log "Please run: sudo $0" - exit 1 + log "This script needs to be run as root to create systemd service." + log "Please run: sudo $0" + exit 1 fi log "Setting up media organizer startup service..." diff --git a/scripts/utils/setup_passwordless_system.sh b/scripts/utils/setup_passwordless_system.sh index 91dec8a..089c1ed 100755 --- a/scripts/utils/setup_passwordless_system.sh +++ b/scripts/utils/setup_passwordless_system.sh @@ -6,34 +6,34 @@ # --reboot: Offer to reboot after setup completion # --logout: Allow restart of LightDM (which will logout the user) -set -e # Exit on any error +set -e # Exit on any error # Check for flags OFFER_REBOOT=false ALLOW_LOGOUT=false for arg in "$@"; do - case $arg in - --reboot) - OFFER_REBOOT=true - shift - ;; - --logout) - ALLOW_LOGOUT=true - shift - ;; - *) - # Unknown option, keep it for sudo check - ;; - esac + case $arg in + --reboot) + OFFER_REBOOT=true + shift + ;; + --logout) + ALLOW_LOGOUT=true + shift + ;; + *) + # Unknown option, keep it for sudo check + ;; + esac done # Function to check and request sudo privileges check_sudo() { - if [[ $EUID -ne 0 ]]; then - echo "This script requires sudo privileges to modify system configurations." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]]; then + echo "This script requires sudo privileges to modify system configurations." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } # Check for sudo privileges first @@ -46,9 +46,9 @@ echo "User: $USER" echo "Original user: ${SUDO_USER:-$USER}" # Verify we have a valid user -if [[ -z "${SUDO_USER}" ]]; then - echo "Error: Could not determine the original user. Please run this script with sudo." - exit 1 +if [[ -z ${SUDO_USER} ]]; then + echo "Error: Could not determine the original user. Please run this script with sudo." + exit 1 fi TARGET_USER="${SUDO_USER}" @@ -56,24 +56,26 @@ echo "Target user for configuration: $TARGET_USER" # Function to backup files backup_file() { - local file="$1" - if [[ -f "$file" ]]; then - local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" - cp "$file" "$backup" - echo "✓ Backed up $file to $backup" - fi + local file="$1" + if [[ -f $file ]]; then + local backup timestamp + timestamp=$(date +%Y%m%d_%H%M%S) + backup="${file}.backup.$timestamp" + cp "$file" "$backup" + echo "✓ Backed up $file to $backup" + fi } # Function to configure passwordless sudo configure_passwordless_sudo() { - echo "" - echo "1. Configuring Passwordless Sudo..." - echo "==================================" - - local sudoers_file="/etc/sudoers.d/99-passwordless-${TARGET_USER}" - - # Create sudoers configuration for passwordless access - cat > "$sudoers_file" << EOF + echo "" + echo "1. Configuring Passwordless Sudo..." + echo "==================================" + + local sudoers_file="/etc/sudoers.d/99-passwordless-${TARGET_USER}" + + # Create sudoers configuration for passwordless access + cat > "$sudoers_file" << EOF # Passwordless sudo configuration for user: ${TARGET_USER} # Created by setup_passwordless_system.sh on $(date) # WARNING: This allows the user to run any command without password @@ -88,55 +90,55 @@ Defaults:${TARGET_USER} !requiretty Defaults:${TARGET_USER} env_keep += "HOME PATH DISPLAY XAUTHORITY" EOF - # Set proper permissions for sudoers file - chmod 440 "$sudoers_file" - - # Verify the sudoers file syntax - if visudo -c -f "$sudoers_file"; then - echo "✓ Passwordless sudo configured for user: $TARGET_USER" - echo "✓ Sudoers file created: $sudoers_file" - else - echo "✗ Error: Invalid sudoers syntax. Removing file for safety." - rm -f "$sudoers_file" - exit 1 - fi + # Set proper permissions for sudoers file + chmod 440 "$sudoers_file" + + # Verify the sudoers file syntax + if visudo -c -f "$sudoers_file"; then + echo "✓ Passwordless sudo configured for user: $TARGET_USER" + echo "✓ Sudoers file created: $sudoers_file" + else + echo "✗ Error: Invalid sudoers syntax. Removing file for safety." + rm -f "$sudoers_file" + exit 1 + fi } # Function to configure lightdm auto-login configure_lightdm_autologin() { - echo "" - echo "2. Configuring LightDM Auto-Login..." - echo "===================================" - - local lightdm_conf="/etc/lightdm/lightdm.conf" - local lightdm_conf_dir="/etc/lightdm/lightdm.conf.d" - local custom_conf="$lightdm_conf_dir/50-autologin.conf" - - # Create lightdm config directory if it doesn't exist - mkdir -p "$lightdm_conf_dir" - - # Backup existing lightdm configuration - backup_file "$lightdm_conf" - - # Check if lightdm is installed - if ! command -v lightdm &> /dev/null; then - echo "Warning: LightDM not found. Installing lightdm..." - pacman -S --noconfirm lightdm lightdm-gtk-greeter - fi - - # Method 1: Update the main lightdm.conf file directly - sed -i "/^#autologin-user=/c\autologin-user=${TARGET_USER}" "$lightdm_conf" - sed -i "/^#autologin-user-timeout=/c\autologin-user-timeout=0" "$lightdm_conf" - sed -i "/^#autologin-session=/c\autologin-session=i3" "$lightdm_conf" - sed -i "/^#autologin-in-background=/c\autologin-in-background=false" "$lightdm_conf" - - # Also set user-session to i3 as fallback - sed -i "/^#user-session=/c\user-session=i3" "$lightdm_conf" - - echo "✓ LightDM auto-login configured in main config file" - - # Method 2: Also create the separate config file for redundancy - cat > "$custom_conf" << EOF + echo "" + echo "2. Configuring LightDM Auto-Login..." + echo "===================================" + + local lightdm_conf="/etc/lightdm/lightdm.conf" + local lightdm_conf_dir="/etc/lightdm/lightdm.conf.d" + local custom_conf="$lightdm_conf_dir/50-autologin.conf" + + # Create lightdm config directory if it doesn't exist + mkdir -p "$lightdm_conf_dir" + + # Backup existing lightdm configuration + backup_file "$lightdm_conf" + + # Check if lightdm is installed + if ! command -v lightdm &> /dev/null; then + echo "Warning: LightDM not found. Installing lightdm..." + pacman -S --noconfirm lightdm lightdm-gtk-greeter + fi + + # Method 1: Update the main lightdm.conf file directly + sed -i "/^#autologin-user=/c\autologin-user=${TARGET_USER}" "$lightdm_conf" + sed -i "/^#autologin-user-timeout=/c\autologin-user-timeout=0" "$lightdm_conf" + sed -i "/^#autologin-session=/c\autologin-session=i3" "$lightdm_conf" + sed -i "/^#autologin-in-background=/c\autologin-in-background=false" "$lightdm_conf" + + # Also set user-session to i3 as fallback + sed -i "/^#user-session=/c\user-session=i3" "$lightdm_conf" + + echo "✓ LightDM auto-login configured in main config file" + + # Method 2: Also create the separate config file for redundancy + cat > "$custom_conf" << EOF # LightDM Auto-Login Configuration # Created by setup_passwordless_system.sh on $(date) @@ -158,37 +160,37 @@ greeter-session=lightdm-gtk-greeter autologin-in-background=false EOF - echo "✓ LightDM auto-login also configured in separate config file: $custom_conf" - - # Enable lightdm service - systemctl enable lightdm.service - echo "✓ LightDM service enabled" - - # Restart lightdm to apply changes only if --logout flag is provided - if [[ "$ALLOW_LOGOUT" == true ]]; then - echo "Restarting LightDM to apply auto-login settings..." - systemctl restart lightdm.service - echo "✓ LightDM restarted" - else - echo "✓ LightDM configuration complete (restart lightdm or reboot to activate auto-login)" - fi + echo "✓ LightDM auto-login also configured in separate config file: $custom_conf" + + # Enable lightdm service + systemctl enable lightdm.service + echo "✓ LightDM service enabled" + + # Restart lightdm to apply changes only if --logout flag is provided + if [[ $ALLOW_LOGOUT == true ]]; then + echo "Restarting LightDM to apply auto-login settings..." + systemctl restart lightdm.service + echo "✓ LightDM restarted" + else + echo "✓ LightDM configuration complete (restart lightdm or reboot to activate auto-login)" + fi } # Function to configure i3 session configure_i3_session() { - echo "" - echo "3. Configuring i3 Session..." - echo "===========================" - - local xsessions_dir="/usr/share/xsessions" - local i3_desktop="$xsessions_dir/i3.desktop" - - # Create xsessions directory if it doesn't exist - mkdir -p "$xsessions_dir" - - # Check if i3.desktop exists, create if not - if [[ ! -f "$i3_desktop" ]]; then - cat > "$i3_desktop" << EOF + echo "" + echo "3. Configuring i3 Session..." + echo "===========================" + + local xsessions_dir="/usr/share/xsessions" + local i3_desktop="$xsessions_dir/i3.desktop" + + # Create xsessions directory if it doesn't exist + mkdir -p "$xsessions_dir" + + # Check if i3.desktop exists, create if not + if [[ ! -f $i3_desktop ]]; then + cat > "$i3_desktop" << EOF [Desktop Entry] Name=i3 Comment=improved dynamic tiling window manager @@ -199,42 +201,42 @@ X-LightDM-DesktopName=i3 DesktopNames=i3 Keywords=tiling;wm;windowmanager;window;manager; EOF - echo "✓ Created i3 desktop session file: $i3_desktop" - else - echo "✓ i3 desktop session file already exists" - fi - - # Ensure user has i3 config directory - local user_home="/home/${TARGET_USER}" - local i3_config_dir="$user_home/.config/i3" - - if [[ ! -d "$i3_config_dir" ]]; then - sudo -u "$TARGET_USER" mkdir -p "$i3_config_dir" - echo "✓ Created i3 config directory for user: $TARGET_USER" - fi + echo "✓ Created i3 desktop session file: $i3_desktop" + else + echo "✓ i3 desktop session file already exists" + fi + + # Ensure user has i3 config directory + local user_home="/home/${TARGET_USER}" + local i3_config_dir="$user_home/.config/i3" + + if [[ ! -d $i3_config_dir ]]; then + sudo -u "$TARGET_USER" mkdir -p "$i3_config_dir" + echo "✓ Created i3 config directory for user: $TARGET_USER" + fi } # Function to configure additional auto-login settings configure_additional_settings() { - echo "" - echo "4. Configuring Additional Settings..." - echo "====================================" - - # Add user to autologin group if it exists - if getent group autologin &> /dev/null; then - usermod -a -G autologin "$TARGET_USER" - echo "✓ Added $TARGET_USER to autologin group" - else - # Create autologin group - groupadd -r autologin - usermod -a -G autologin "$TARGET_USER" - echo "✓ Created autologin group and added $TARGET_USER" - fi - - # Configure pam for auto-login (if needed) - local pam_lightdm="/etc/pam.d/lightdm-autologin" - if [[ ! -f "$pam_lightdm" ]]; then - cat > "$pam_lightdm" << EOF + echo "" + echo "4. Configuring Additional Settings..." + echo "====================================" + + # Add user to autologin group if it exists + if getent group autologin &> /dev/null; then + usermod -a -G autologin "$TARGET_USER" + echo "✓ Added $TARGET_USER to autologin group" + else + # Create autologin group + groupadd -r autologin + usermod -a -G autologin "$TARGET_USER" + echo "✓ Created autologin group and added $TARGET_USER" + fi + + # Configure pam for auto-login (if needed) + local pam_lightdm="/etc/pam.d/lightdm-autologin" + if [[ ! -f $pam_lightdm ]]; then + cat > "$pam_lightdm" << EOF #%PAM-1.0 # LightDM auto-login PAM configuration # Created by setup_passwordless_system.sh on $(date) @@ -247,98 +249,98 @@ password include system-local-login session include system-local-login session optional pam_gnome_keyring.so auto_start EOF - echo "✓ Created PAM configuration for auto-login" - fi + echo "✓ Created PAM configuration for auto-login" + fi } # Function to test configurations test_configurations() { - echo "" - echo "5. Testing Configurations..." - echo "===========================" - - # Test sudo configuration - echo "Testing passwordless sudo..." - if sudo -u "$TARGET_USER" sudo -n true 2>/dev/null; then - echo "✓ Passwordless sudo test passed" - else - echo "! Passwordless sudo test failed (may require logout/login)" - fi - - # Test lightdm configuration - echo "Testing LightDM configuration..." - if lightdm --test-mode --debug 2>/dev/null | grep -q "seat"; then - echo "✓ LightDM configuration test passed" - else - echo "! LightDM configuration test completed (check logs if issues occur)" - fi - - # Verify user is in autologin group - if groups "$TARGET_USER" | grep -q autologin; then - echo "✓ User is in autologin group" - else - echo "! User may not be in autologin group" - fi + echo "" + echo "5. Testing Configurations..." + echo "===========================" + + # Test sudo configuration + echo "Testing passwordless sudo..." + if sudo -u "$TARGET_USER" sudo -n true 2> /dev/null; then + echo "✓ Passwordless sudo test passed" + else + echo "! Passwordless sudo test failed (may require logout/login)" + fi + + # Test lightdm configuration + echo "Testing LightDM configuration..." + if lightdm --test-mode --debug 2> /dev/null | grep -q "seat"; then + echo "✓ LightDM configuration test passed" + else + echo "! LightDM configuration test completed (check logs if issues occur)" + fi + + # Verify user is in autologin group + if groups "$TARGET_USER" | grep -q autologin; then + echo "✓ User is in autologin group" + else + echo "! User may not be in autologin group" + fi } # Function to show security warnings show_security_warnings() { - echo "" - echo "⚠️ SECURITY WARNINGS ⚠️" - echo "========================" - echo "" - echo "The following security changes have been made:" - echo "" - echo "1. PASSWORDLESS SUDO:" - echo " • User '$TARGET_USER' can now run ANY command as root without password" - echo " • This includes system-critical operations and file modifications" - echo " • Malicious software running as this user can gain full system access" - echo "" - echo "2. AUTO-LOGIN:" - echo " • System automatically logs in user '$TARGET_USER' on boot" - echo " • No password required to access the desktop environment" - echo " • Physical access to the machine = full user access" - echo "" - echo "3. RECOMMENDATIONS:" - echo " • Use full disk encryption to protect against physical access" - echo " • Ensure the system is in a physically secure location" - echo " • Consider using this only on personal/development machines" - echo " • Regularly monitor system logs for unauthorized access" - echo " • Keep the system updated and use a firewall" - echo "" - echo "4. TO DISABLE THESE SETTINGS:" - echo " • Remove passwordless sudo: sudo rm /etc/sudoers.d/99-passwordless-${TARGET_USER}" - echo " • Disable auto-login: sudo rm /etc/lightdm/lightdm.conf.d/50-autologin.conf" - echo " • Restart LightDM: sudo systemctl restart lightdm" - echo "" + echo "" + echo "⚠️ SECURITY WARNINGS ⚠️" + echo "========================" + echo "" + echo "The following security changes have been made:" + echo "" + echo "1. PASSWORDLESS SUDO:" + echo " • User '$TARGET_USER' can now run ANY command as root without password" + echo " • This includes system-critical operations and file modifications" + echo " • Malicious software running as this user can gain full system access" + echo "" + echo "2. AUTO-LOGIN:" + echo " • System automatically logs in user '$TARGET_USER' on boot" + echo " • No password required to access the desktop environment" + echo " • Physical access to the machine = full user access" + echo "" + echo "3. RECOMMENDATIONS:" + echo " • Use full disk encryption to protect against physical access" + echo " • Ensure the system is in a physically secure location" + echo " • Consider using this only on personal/development machines" + echo " • Regularly monitor system logs for unauthorized access" + echo " • Keep the system updated and use a firewall" + echo "" + echo "4. TO DISABLE THESE SETTINGS:" + echo " • Remove passwordless sudo: sudo rm /etc/sudoers.d/99-passwordless-${TARGET_USER}" + echo " • Disable auto-login: sudo rm /etc/lightdm/lightdm.conf.d/50-autologin.conf" + echo " • Restart LightDM: sudo systemctl restart lightdm" + echo "" } # Function to show final instructions show_final_instructions() { - echo "" - echo "==========================================" - echo "Passwordless System Setup Complete" - echo "==========================================" - echo "Summary:" - echo "✓ Passwordless sudo configured for user: $TARGET_USER" - echo "✓ LightDM auto-login configured" - echo "✓ i3 session configured" - echo "✓ Additional auto-login settings applied" - echo "" - echo "Changes will take effect after:" - echo "• Logout/login for sudo changes" - echo "• System reboot for auto-login" - echo "" - echo "To verify after reboot:" - echo " sudo whoami # Should not ask for password" - echo " systemctl status lightdm # Should show auto-login active" - echo "" - echo "Configuration files created:" - echo " /etc/sudoers.d/99-passwordless-${TARGET_USER}" - echo " /etc/lightdm/lightdm.conf.d/50-autologin.conf" - echo " /etc/pam.d/lightdm-autologin" - echo "" - echo "IMPORTANT: Reboot recommended to activate all changes!" + echo "" + echo "==========================================" + echo "Passwordless System Setup Complete" + echo "==========================================" + echo "Summary:" + echo "✓ Passwordless sudo configured for user: $TARGET_USER" + echo "✓ LightDM auto-login configured" + echo "✓ i3 session configured" + echo "✓ Additional auto-login settings applied" + echo "" + echo "Changes will take effect after:" + echo "• Logout/login for sudo changes" + echo "• System reboot for auto-login" + echo "" + echo "To verify after reboot:" + echo " sudo whoami # Should not ask for password" + echo " systemctl status lightdm # Should show auto-login active" + echo "" + echo "Configuration files created:" + echo " /etc/sudoers.d/99-passwordless-${TARGET_USER}" + echo " /etc/lightdm/lightdm.conf.d/50-autologin.conf" + echo " /etc/pam.d/lightdm-autologin" + echo "" + echo "IMPORTANT: Reboot recommended to activate all changes!" } # Main execution @@ -351,22 +353,22 @@ show_security_warnings show_final_instructions # Only offer reboot if --reboot flag was provided -if [[ "$OFFER_REBOOT" == true ]]; then - echo "" - echo "Would you like to reboot now to activate all changes?" - read -p "Reboot system now? (y/N): " -n 1 -r - echo +if [[ $OFFER_REBOOT == true ]]; then + echo "" + echo "Would you like to reboot now to activate all changes?" + read -p "Reboot system now? (y/N): " -n 1 -r + echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - echo "Rebooting system in 5 seconds..." - sleep 5 - reboot - else - echo "Remember to reboot when convenient to activate all changes." - fi -else - echo "" - echo "Setup completed successfully." + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Rebooting system in 5 seconds..." + sleep 5 + reboot + else echo "Remember to reboot when convenient to activate all changes." - echo "To automatically prompt for reboot in the future, use: $0 --reboot" + fi +else + echo "" + echo "Setup completed successfully." + echo "Remember to reboot when convenient to activate all changes." + echo "To automatically prompt for reboot in the future, use: $0 --reboot" fi diff --git a/scripts/utils/sort_downloads.sh b/scripts/utils/sort_downloads.sh index 71ae20e..5f89d73 100755 --- a/scripts/utils/sort_downloads.sh +++ b/scripts/utils/sort_downloads.sh @@ -3,23 +3,33 @@ # Function to sort files in a given directory sort_files() { local dir=$1 - cd "$dir" + cd "$dir" || return 1 + + local old_nullglob + old_nullglob=$(shopt -p nullglob || true) + shopt -s nullglob # Create directories if they do not exist mkdir -p images videos documents # Move video files to the videos folder - mv *.webm *.mp4 *.mkv *.avi *.mov *.flv videos/ 2>/dev/null + mv -- ./*.webm ./*.mp4 ./*.mkv ./*.avi ./*.mov ./*.flv videos/ 2> /dev/null # Move image files to the images folder - mv *.png *.jpg *.jpeg *.gif *.webp *.bmp images/ 2>/dev/null + mv -- ./*.png ./*.jpg ./*.jpeg ./*.gif ./*.webp ./*.bmp images/ 2> /dev/null # Move document files to the documents folder - mv *.pdf *.doc *.docx *.txt *.odt documents/ 2>/dev/null + mv -- ./*.pdf ./*.doc ./*.docx ./*.txt ./*.odt documents/ 2> /dev/null + + if [[ -n $old_nullglob ]]; then + eval "$old_nullglob" + else + shopt -u nullglob + fi } # Sort files in the Downloads folder sort_files ~/Downloads # Sort files in the home folder -sort_files ~ \ No newline at end of file +sort_files ~ diff --git a/scripts/utils/steam_compatibility.sh b/scripts/utils/steam_compatibility.sh index d904f07..e847815 100755 --- a/scripts/utils/steam_compatibility.sh +++ b/scripts/utils/steam_compatibility.sh @@ -20,16 +20,16 @@ set -euo pipefail SCRIPT_NAME=${0##*/} ABORT=0 on_abort() { - ABORT=1 - log "Aborted by user" - exit 130 + ABORT=1 + log "Aborted by user" + exit 130 } trap on_abort INT TERM # --------------------------- CLI args --------------------------- usage() { - cat <&2; } -die() { printf "[%s] ERROR: %s\n" "$SCRIPT_NAME" "$*" >&2; exit 1; } -vlog() { if [[ $VERBOSE -eq 1 ]]; then printf "[%s][verbose] %s\n" "$SCRIPT_NAME" "$*" >&2; fi } +die() { + printf "[%s] ERROR: %s\n" "$SCRIPT_NAME" "$*" >&2 + exit 1 +} +vlog() { if [[ $VERBOSE -eq 1 ]]; then printf "[%s][verbose] %s\n" "$SCRIPT_NAME" "$*" >&2; fi; } require_cmd() { - command -v "$1" >/dev/null 2>&1 || die "Missing dependency: $1" + command -v "$1" > /dev/null 2>&1 || die "Missing dependency: $1" } for cmd in curl jq awk sed grep sort lspci free uname; do - require_cmd "$cmd" + require_cmd "$cmd" done HAS_TIMEOUT=0 -if command -v timeout >/dev/null 2>&1; then - HAS_TIMEOUT=1 +if command -v timeout > /dev/null 2>&1; then + HAS_TIMEOUT=1 fi # Safe HTTP GET with optional timeout if available http_get() { - local url="$1" - shift || true - local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36" - if [[ $HAS_TIMEOUT -eq 1 ]]; then - timeout 12s curl -sSL --compressed --retry 2 --retry-delay 0.5 --retry-connrefused \ - -H "User-Agent: $ua" -H 'Accept: application/json, text/plain, */*' "$url" "$@" 2>/dev/null - else - curl -sSL --compressed --retry 2 --retry-delay 0.5 --retry-connrefused \ - -H "User-Agent: $ua" -H 'Accept: application/json, text/plain, */*' "$url" "$@" 2>/dev/null - fi + local url="$1" + shift || true + local ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36" + if [[ $HAS_TIMEOUT -eq 1 ]]; then + timeout 12s curl -sSL --compressed --retry 2 --retry-delay 0.5 --retry-connrefused \ + -H "User-Agent: $ua" -H 'Accept: application/json, text/plain, */*' "$url" "$@" 2> /dev/null + else + curl -sSL --compressed --retry 2 --retry-delay 0.5 --retry-connrefused \ + -H "User-Agent: $ua" -H 'Accept: application/json, text/plain, */*' "$url" "$@" 2> /dev/null + fi } # --------------------------- System detection --------------------------- @@ -100,94 +112,127 @@ SYSTEM_CPU_CLASS="unknown" SYSTEM_GPU_VENDOR="unknown" SYSTEM_RAM_GB=0 SYSTEM_ARCH="$(uname -m || echo unknown)" -SYSTEM_OS="linux" -to_int() { awk '{gsub(/[^0-9]/,""); if($0=="") print 0; else print $0}' <<<"$1"; } +to_int() { awk '{gsub(/[^0-9]/,""); if($0=="") print 0; else print $0}' <<< "$1"; } detect_system() { - # CPU model - if command -v lscpu >/dev/null 2>&1; then - SYSTEM_CPU_MODEL=$(lscpu | awk -F': *' '/Model name/ {print $2; exit}') - fi - if [[ -z "$SYSTEM_CPU_MODEL" && -r /proc/cpuinfo ]]; then - SYSTEM_CPU_MODEL=$(awk -F': *' '/model name/ {print $2; exit}' /proc/cpuinfo) - fi - SYSTEM_CPU_MODEL=${SYSTEM_CPU_MODEL:-unknown} + # CPU model + if command -v lscpu > /dev/null 2>&1; then + SYSTEM_CPU_MODEL=$(lscpu | awk -F': *' '/Model name/ {print $2; exit}') + fi + if [[ -z $SYSTEM_CPU_MODEL && -r /proc/cpuinfo ]]; then + SYSTEM_CPU_MODEL=$(awk -F': *' '/model name/ {print $2; exit}' /proc/cpuinfo) + fi + SYSTEM_CPU_MODEL=${SYSTEM_CPU_MODEL:-unknown} - # CPU class (very rough) - lc_model=$(tr '[:upper:]' '[:lower:]' <<<"$SYSTEM_CPU_MODEL") - if grep -qiE 'i9-|core\(tm\) i9| ryzen 9' <<<"$lc_model"; then SYSTEM_CPU_CLASS="tier4"; - elif grep -qiE 'i7-|core\(tm\) i7| ryzen 7' <<<"$lc_model"; then SYSTEM_CPU_CLASS="tier3"; - elif grep -qiE 'i5-|core\(tm\) i5| ryzen 5' <<<"$lc_model"; then SYSTEM_CPU_CLASS="tier2"; - elif grep -qiE 'i3-|core\(tm\) i3| ryzen 3| pentium|celeron|atom' <<<"$lc_model"; then SYSTEM_CPU_CLASS="tier1"; - else SYSTEM_CPU_CLASS="tier2"; fi + # CPU class (very rough) + lc_model=$(tr '[:upper:]' '[:lower:]' <<< "$SYSTEM_CPU_MODEL") + if grep -qiE 'i9-|core\(tm\) i9| ryzen 9' <<< "$lc_model"; then + SYSTEM_CPU_CLASS="tier4" + elif grep -qiE 'i7-|core\(tm\) i7| ryzen 7' <<< "$lc_model"; then + SYSTEM_CPU_CLASS="tier3" + elif grep -qiE 'i5-|core\(tm\) i5| ryzen 5' <<< "$lc_model"; then + SYSTEM_CPU_CLASS="tier2" + elif grep -qiE 'i3-|core\(tm\) i3| ryzen 3| pentium|celeron|atom' <<< "$lc_model"; then + SYSTEM_CPU_CLASS="tier1" + else SYSTEM_CPU_CLASS="tier2"; fi - # GPU vendor - local vga - vga=$(lspci 2>/dev/null | grep -iE 'vga|3d|display' | head -n1 || true) - lc_vga=$(tr '[:upper:]' '[:lower:]' <<<"$vga") - if grep -q 'nvidia' <<<"$lc_vga"; then SYSTEM_GPU_VENDOR="nvidia"; - elif grep -q -E 'amd|ati|radeon' <<<"$lc_vga"; then SYSTEM_GPU_VENDOR="amd"; - elif grep -q 'intel' <<<"$lc_vga"; then SYSTEM_GPU_VENDOR="intel"; - else SYSTEM_GPU_VENDOR="unknown"; fi + # GPU vendor + local vga + vga=$(lspci 2> /dev/null | grep -iE 'vga|3d|display' | head -n1 || true) + lc_vga=$(tr '[:upper:]' '[:lower:]' <<< "$vga") + if grep -q 'nvidia' <<< "$lc_vga"; then + SYSTEM_GPU_VENDOR="nvidia" + elif grep -q -E 'amd|ati|radeon' <<< "$lc_vga"; then + SYSTEM_GPU_VENDOR="amd" + elif grep -q 'intel' <<< "$lc_vga"; then + SYSTEM_GPU_VENDOR="intel" + else SYSTEM_GPU_VENDOR="unknown"; fi - # RAM GB - local mem_kb - mem_kb=$(awk '/MemTotal/ {print $2; exit}' /proc/meminfo 2>/dev/null || echo 0) - if [[ "$mem_kb" -gt 0 ]]; then - SYSTEM_RAM_GB=$(( (mem_kb + 1023*1024) / (1024*1024) )) - else - local mem_mb - mem_mb=$(free -m | awk '/Mem:/ {print $2; exit}') - SYSTEM_RAM_GB=$(( (mem_mb + 1023) / 1024 )) - fi + # RAM GB + local mem_kb + mem_kb=$(awk '/MemTotal/ {print $2; exit}' /proc/meminfo 2> /dev/null || echo 0) + if [[ $mem_kb -gt 0 ]]; then + SYSTEM_RAM_GB=$(((mem_kb + 1023 * 1024) / (1024 * 1024))) + else + local mem_mb + mem_mb=$(free -m | awk '/Mem:/ {print $2; exit}') + SYSTEM_RAM_GB=$(((mem_mb + 1023) / 1024)) + fi } cpu_class_rank() { - case "$1" in - tier1) echo 1 ;; - tier2) echo 2 ;; - tier3) echo 3 ;; - tier4) echo 4 ;; - *) echo 2 ;; - esac + case "$1" in + tier1) echo 1 ;; + tier2) echo 2 ;; + tier3) echo 3 ;; + tier4) echo 4 ;; + *) echo 2 ;; + esac } required_cpu_rank_from_text() { - local t=$(tr '[:upper:]' '[:lower:]' <<<"$1") - if grep -qE 'i9|ryzen 9' <<<"$t"; then echo 4; return; fi - if grep -qE 'i7|ryzen 7' <<<"$t"; then echo 3; return; fi - if grep -qE 'i5|ryzen 5' <<<"$t"; then echo 2; return; fi - if grep -qE 'i3|ryzen 3|pentium|celeron|atom' <<<"$t"; then echo 1; return; fi - echo 2 + local t + t=$(tr '[:upper:]' '[:lower:]' <<< "$1") + if grep -qE 'i9|ryzen 9' <<< "$t"; then + echo 4 + return + fi + if grep -qE 'i7|ryzen 7' <<< "$t"; then + echo 3 + return + fi + if grep -qE 'i5|ryzen 5' <<< "$t"; then + echo 2 + return + fi + if grep -qE 'i3|ryzen 3|pentium|celeron|atom' <<< "$t"; then + echo 1 + return + fi + echo 2 } gpu_vendor_required_from_text() { - local t=$(tr '[:upper:]' '[:lower:]' <<<"$1") - if grep -qE 'nvidia|geforce|gtx|rtx' <<<"$t"; then echo nvidia; return; fi - if grep -qE 'amd|radeon|rx[ -]?[0-9]' <<<"$t"; then echo amd; return; fi - if grep -qE 'intel( graphics| arc| iris| hd)' <<<"$t"; then echo intel; return; fi - echo unknown + local t + t=$(tr '[:upper:]' '[:lower:]' <<< "$1") + if grep -qE 'nvidia|geforce|gtx|rtx' <<< "$t"; then + echo nvidia + return + fi + if grep -qE 'amd|radeon|rx[ -]?[0-9]' <<< "$t"; then + echo amd + return + fi + if grep -qE 'intel( graphics| arc| iris| hd)' <<< "$t"; then + echo intel + return + fi + echo unknown } strip_html() { - sed -E 's/<[^>]+>//g; s/ / /g; s/&/\&/g; s/\r//g' <<<"$1" + sed -E 's/<[^>]+>//g; s/ / /g; s/&/\&/g; s/\r//g' <<< "$1" } parse_ram_gb() { - # Extract first RAM mention and convert to GB integer - local text="$1" - local num unit val - # Prefer GB - num=$(grep -oiE '([0-9]+)\s*(gb|gib)' <<<"$text" | head -n1 | grep -oiE '^[0-9]+' || true) - if [[ -n "$num" ]]; then echo "$num"; return; fi - # Try MB - num=$(grep -oiE '([0-9]+)\s*(mb|mib)' <<<"$text" | head -n1 | grep -oiE '^[0-9]+' || true) - if [[ -n "$num" ]]; then - val=$(( (num + 1023) / 1024 )) - echo "$val"; return - fi - echo 0 + # Extract first RAM mention and convert to GB integer + local text="$1" + local num val + # Prefer GB + num=$(grep -oiE '([0-9]+)\s*(gb|gib)' <<< "$text" | head -n1 | grep -oiE '^[0-9]+' || true) + if [[ -n $num ]]; then + echo "$num" + return + fi + # Try MB + num=$(grep -oiE '([0-9]+)\s*(mb|mib)' <<< "$text" | head -n1 | grep -oiE '^[0-9]+' || true) + if [[ -n $num ]]; then + val=$(((num + 1023) / 1024)) + echo "$val" + return + fi + echo 0 } # --------------------------- Steam data --------------------------- @@ -200,26 +245,26 @@ CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/steam-compat-check" RESULTS_CACHE="$CACHE_DIR/results.tsv" load_credentials() { - # Prefer environment, else config file - if [[ -n "${STEAM_API_KEY:-}" && -n "${STEAM_ID64:-}" ]]; then - return 0 - fi - if [[ -r "$CONFIG_FILE" ]]; then - # shellcheck disable=SC1090 - . "$CONFIG_FILE" || true - fi - if [[ -z "${STEAM_API_KEY:-}" || -z "${STEAM_ID64:-}" ]]; then - return 1 - fi - return 0 + # Prefer environment, else config file + if [[ -n ${STEAM_API_KEY:-} && -n ${STEAM_ID64:-} ]]; then + return 0 + fi + if [[ -r $CONFIG_FILE ]]; then + # shellcheck disable=SC1090 + . "$CONFIG_FILE" || true + fi + if [[ -z ${STEAM_API_KEY:-} || -z ${STEAM_ID64:-} ]]; then + return 1 + fi + return 0 } save_credentials() { - local key="$1" id="$2" - mkdir -p "$CONFIG_DIR" - chmod 700 "$CONFIG_DIR" 2>/dev/null || true - umask 177 - cat > "$CONFIG_FILE" < /dev/null || true + umask 177 + cat > "$CONFIG_FILE" << EOF # Saved by $SCRIPT_NAME STEAM_API_KEY="$key" STEAM_ID64="$id" @@ -227,95 +272,95 @@ EOF } prompt_for_credentials() { - if [[ ! -t 0 ]]; then - die "STEAM_API_KEY/STEAM_ID64 not set and input is non-interactive. Export them or create $CONFIG_FILE." - fi - echo "Steam Web API credentials are required to scan your full library." - echo - echo "Where to get them:" - echo "- Steam Web API Key: https://steamcommunity.com/dev/apikey" - echo " Log in, set any domain (e.g., 127.0.0.1), then copy the key." - echo "- SteamID64 (17-digit ID starting with 765):" - echo " * Easiest: https://steamid.io/ (paste your profile URL to get the 64-bit ID)" - echo " * Or enable URL bar in Steam (Settings > Interface), open your profile; the URL contains the ID." - echo - local key id - read -r -p "Enter Steam Web API Key: " key - read -r -p "Enter Steam 64-bit ID (begins with 765…): " id - if [[ -z "$key" || -z "$id" ]]; then - die "Credentials not provided. Exiting." - fi - # Light validation for ID64 - if ! grep -qE '^765[0-9]{14}$' <<<"$id"; then - log "Warning: Steam ID64 format unexpected; continuing anyway." - fi - STEAM_API_KEY="$key" - STEAM_ID64="$id" - export STEAM_API_KEY STEAM_ID64 - save_credentials "$STEAM_API_KEY" "$STEAM_ID64" - log "Saved credentials to $CONFIG_FILE" + if [[ ! -t 0 ]]; then + die "STEAM_API_KEY/STEAM_ID64 not set and input is non-interactive. Export them or create $CONFIG_FILE." + fi + echo "Steam Web API credentials are required to scan your full library." + echo + echo "Where to get them:" + echo "- Steam Web API Key: https://steamcommunity.com/dev/apikey" + echo " Log in, set any domain (e.g., 127.0.0.1), then copy the key." + echo "- SteamID64 (17-digit ID starting with 765):" + echo " * Easiest: https://steamid.io/ (paste your profile URL to get the 64-bit ID)" + echo " * Or enable URL bar in Steam (Settings > Interface), open your profile; the URL contains the ID." + echo + local key id + read -r -p "Enter Steam Web API Key: " key + read -r -p "Enter Steam 64-bit ID (begins with 765…): " id + if [[ -z $key || -z $id ]]; then + die "Credentials not provided. Exiting." + fi + # Light validation for ID64 + if ! grep -qE '^765[0-9]{14}$' <<< "$id"; then + log "Warning: Steam ID64 format unexpected; continuing anyway." + fi + STEAM_API_KEY="$key" + STEAM_ID64="$id" + export STEAM_API_KEY STEAM_ID64 + save_credentials "$STEAM_API_KEY" "$STEAM_ID64" + log "Saved credentials to $CONFIG_FILE" } -STEAM_DIRS=( "$HOME/.steam/steam" "$HOME/.local/share/Steam" ) +STEAM_DIRS=("$HOME/.steam/steam" "$HOME/.local/share/Steam") find_steamapps_dirs() { - local dirs=() - for base in "${STEAM_DIRS[@]}"; do - [[ -d "$base" ]] || continue - if [[ -f "$base/steamapps/libraryfolders.vdf" ]]; then - # Newer format includes nested objects with paths - local paths - paths=$(grep -oE '"path"\s+"[^"]+"' "$base/steamapps/libraryfolders.vdf" | sed -E 's/.*"([^"]+)"/\1/' || true) - if [[ -n "$paths" ]]; then - while IFS= read -r p; do - [[ -d "$p/steamapps" ]] && dirs+=("$p/steamapps") - done <<<"$paths" - fi - fi - [[ -d "$base/steamapps" ]] && dirs+=("$base/steamapps") - done - # de-dupe - printf "%s\n" "${dirs[@]}" 2>/dev/null | awk '!seen[$0]++' + local dirs=() + for base in "${STEAM_DIRS[@]}"; do + [[ -d $base ]] || continue + if [[ -f "$base/steamapps/libraryfolders.vdf" ]]; then + # Newer format includes nested objects with paths + local paths + paths=$(grep -oE '"path"\s+"[^"]+"' "$base/steamapps/libraryfolders.vdf" | sed -E 's/.*"([^"]+)"/\1/' || true) + if [[ -n $paths ]]; then + while IFS= read -r p; do + [[ -d "$p/steamapps" ]] && dirs+=("$p/steamapps") + done <<< "$paths" + fi + fi + [[ -d "$base/steamapps" ]] && dirs+=("$base/steamapps") + done + # de-dupe + printf "%s\n" "${dirs[@]}" 2> /dev/null | awk '!seen[$0]++' } list_installed_games() { - local d appid name - while IFS= read -r d; do - [[ -d "$d" ]] || continue - for mf in "$d"/appmanifest_*.acf; do - [[ -f "$mf" ]] || continue - appid=$(grep -oE '"appid"\s+"[0-9]+"' "$mf" | sed -E 's/.*"([0-9]+)"/\1/' | head -n1) - name=$(grep -oE '"name"\s+"[^"]+"' "$mf" | sed -E 's/.*"([^"]+)"/\1/' | head -n1) - if [[ -n "$appid" ]]; then - printf "%s\t%s\n" "$appid" "${name:-Unknown}" - fi - done - done < <(find_steamapps_dirs) + local d appid name + while IFS= read -r d; do + [[ -d $d ]] || continue + for mf in "$d"/appmanifest_*.acf; do + [[ -f $mf ]] || continue + appid=$(grep -oE '"appid"\s+"[0-9]+"' "$mf" | sed -E 's/.*"([0-9]+)"/\1/' | head -n1) + name=$(grep -oE '"name"\s+"[^"]+"' "$mf" | sed -E 's/.*"([^"]+)"/\1/' | head -n1) + if [[ -n $appid ]]; then + printf "%s\t%s\n" "$appid" "${name:-Unknown}" + fi + done + done < <(find_steamapps_dirs) } list_owned_games_via_api() { - local key="${STEAM_API_KEY:-}" sid="${STEAM_ID64:-}" - if [[ -z "$key" || -z "$sid" ]]; then - return 1 - fi - local url="https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${key}&steamid=${sid}&include_appinfo=1&include_played_free_games=1&format=json" - http_get "$url" | jq -r '.response.games[]? | "\(.appid)\t\(.name)"' || return 1 + local key="${STEAM_API_KEY:-}" sid="${STEAM_ID64:-}" + if [[ -z $key || -z $sid ]]; then + return 1 + fi + local url="https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${key}&steamid=${sid}&include_appinfo=1&include_played_free_games=1&format=json" + http_get "$url" | jq -r '.response.games[]? | "\(.appid)\t\(.name)"' || return 1 } fetch_appdetails_json() { - local appid="$1" - # Using store API (no key) - local url="https://store.steampowered.com/api/appdetails?appids=${appid}&l=en&cc=us" - http_get "$url" || true + local appid="$1" + # Using store API (no key) + local url="https://store.steampowered.com/api/appdetails?appids=${appid}&l=en&cc=us" + http_get "$url" || true } extract_requirements_and_platforms() { - # Input: JSON from appdetails; Output: TSV of fields - # Fields: success, linux, windows, mac, min_text, rec_text, type - local appid="$1" json="$2" - # Some apps return {"APPID": {"success":true, "data":{...}}} - local out - out=$(jq -r --arg APP "$appid" ' + # Input: JSON from appdetails; Output: TSV of fields + # Fields: success, linux, windows, mac, min_text, rec_text, type + local appid="$1" json="$2" + # Some apps return {"APPID": {"success":true, "data":{...}}} + local out + out=$(jq -r --arg APP "$appid" ' .[$APP] as $root | if ($root.success==true and ($root.data|type)=="object") then ($root.data.platforms.linux // false) as $linux | ($root.data.platforms.windows // false) as $windows | @@ -327,18 +372,18 @@ extract_requirements_and_platforms() { ["ok", ($linux|tostring), ($windows|tostring), ($mac|tostring), ($min|tostring), ($rec|tostring), ($type|tostring)] | @tsv else ["fail", "false", "false", "false", "", "", ""] | @tsv - end' 2>/dev/null <<<"$json") || true - if [[ -z "$out" ]]; then - out=$'fail false false false ' - fi - printf '%s\n' "$out" + end' 2> /dev/null <<< "$json") || true + if [[ -z $out ]]; then + out=$'fail false false false ' + fi + printf '%s\n' "$out" } # Read JSON from stdin (avoids storing large/binary data in variables) extract_requirements_and_platforms_stdin() { - local appid="$1" - local out - out=$(jq -r --arg APP "$appid" ' + local appid="$1" + local out + out=$(jq -r --arg APP "$appid" ' .[$APP] as $root | if ($root.success==true and ($root.data|type)=="object") then ($root.data.platforms.linux // false) as $linux | ($root.data.platforms.windows // false) as $windows | @@ -349,270 +394,270 @@ extract_requirements_and_platforms_stdin() { ["ok", ($linux|tostring), ($windows|tostring), ($mac|tostring), ($min|tostring), ($rec|tostring), ($type|tostring)] | @tsv else ["fail", "false", "false", "false", "", "", ""] | @tsv - end' 2>/dev/null) || true - if [[ -z "$out" ]]; then - out=$'fail\tfalse\tfalse\tfalse\t\t\t' - fi - printf '%s\n' "$out" + end' 2> /dev/null) || true + if [[ -z $out ]]; then + out=$'fail\tfalse\tfalse\tfalse\t\t\t' + fi + printf '%s\n' "$out" } score_game() { - local linux_support="$1" min_txt="$2" rec_txt="$3" - local score=0 + local linux_support="$1" min_txt="$2" rec_txt="$3" + local score=0 - # Linux platform support - if [[ "$linux_support" == "true" ]]; then - score=$((score + 50)) - else - score=$((score + 20)) # Assume Proton potential - fi + # Linux platform support + if [[ $linux_support == "true" ]]; then + score=$((score + 50)) + else + score=$((score + 20)) # Assume Proton potential + fi - local min_plain rec_plain - min_plain=$(strip_html "$min_txt") - rec_plain=$(strip_html "$rec_txt") + local min_plain rec_plain + min_plain=$(strip_html "$min_txt") + rec_plain=$(strip_html "$rec_txt") - local min_ram rec_ram - min_ram=$(parse_ram_gb "$min_plain") - rec_ram=$(parse_ram_gb "$rec_plain") + local min_ram rec_ram + min_ram=$(parse_ram_gb "$min_plain") + rec_ram=$(parse_ram_gb "$rec_plain") - # RAM checks - if [[ "$min_ram" -gt 0 ]]; then - if [[ "$SYSTEM_RAM_GB" -ge "$min_ram" ]]; then score=$((score + 15)); else score=$((score - 30)); fi - fi - if [[ "$rec_ram" -gt 0 ]]; then - if [[ "$SYSTEM_RAM_GB" -ge "$rec_ram" ]]; then score=$((score + 10)); else score=$((score - 10)); fi - fi + # RAM checks + if [[ $min_ram -gt 0 ]]; then + if [[ $SYSTEM_RAM_GB -ge $min_ram ]]; then score=$((score + 15)); else score=$((score - 30)); fi + fi + if [[ $rec_ram -gt 0 ]]; then + if [[ $SYSTEM_RAM_GB -ge $rec_ram ]]; then score=$((score + 10)); else score=$((score - 10)); fi + fi - # CPU checks (very rough tiers) - local req_rank sys_rank - req_rank=$(required_cpu_rank_from_text "$min_plain $rec_plain") - sys_rank=$(cpu_class_rank "$SYSTEM_CPU_CLASS") - if [[ "$sys_rank" -ge "$req_rank" ]]; then score=$((score + 10)); else score=$((score - 10)); fi + # CPU checks (very rough tiers) + local req_rank sys_rank + req_rank=$(required_cpu_rank_from_text "$min_plain $rec_plain") + sys_rank=$(cpu_class_rank "$SYSTEM_CPU_CLASS") + if [[ $sys_rank -ge $req_rank ]]; then score=$((score + 10)); else score=$((score - 10)); fi - # GPU vendor hints - local req_gpu vendor - req_gpu=$(gpu_vendor_required_from_text "$min_plain $rec_plain") - vendor="$SYSTEM_GPU_VENDOR" - if [[ "$req_gpu" == "unknown" ]]; then - score=$((score + 5)) - elif [[ "$req_gpu" == "$vendor" ]]; then - score=$((score + 10)) - else - score=$((score - 10)) - fi + # GPU vendor hints + local req_gpu vendor + req_gpu=$(gpu_vendor_required_from_text "$min_plain $rec_plain") + vendor="$SYSTEM_GPU_VENDOR" + if [[ $req_gpu == "unknown" ]]; then + score=$((score + 5)) + elif [[ $req_gpu == "$vendor" ]]; then + score=$((score + 10)) + else + score=$((score - 10)) + fi - # 64-bit OS requirement - if grep -qi '64-?bit' <<<"$min_plain $rec_plain"; then - if [[ "$SYSTEM_ARCH" == "x86_64" || "$SYSTEM_ARCH" == "aarch64" ]]; then - score=$((score + 5)) - else - score=$((score - 20)) - fi - fi + # 64-bit OS requirement + if grep -qi '64-?bit' <<< "$min_plain $rec_plain"; then + if [[ $SYSTEM_ARCH == "x86_64" || $SYSTEM_ARCH == "aarch64" ]]; then + score=$((score + 5)) + else + score=$((score - 20)) + fi + fi - printf "%s\t%s\t%s\n" "$score" "$min_ram" "$rec_ram" + printf "%s\t%s\t%s\n" "$score" "$min_ram" "$rec_ram" } print_header() { - printf "%-5s %-8s %-6s %-6s %-8s %-9s %s\n" "Rank" "Score" "MinRAM" "RecRAM" "Linux" "ProtonDB" "Title" + printf "%-5s %-8s %-6s %-6s %-8s %-9s %s\n" "Rank" "Score" "MinRAM" "RecRAM" "Linux" "ProtonDB" "Title" } check_network_or_exit() { - # Quick probe to Steam Store API; exit early if not reachable - local probe_url="https://store.steampowered.com/api/appdetails?appids=10&l=en&cc=us" - if ! http_get "$probe_url" | jq -e '."10".success == true' >/dev/null 2>&1; then - log "Warning: store.steampowered.com probe failed (network or rate-limit). Continuing and handling per-app." - fi + # Quick probe to Steam Store API; exit early if not reachable + local probe_url="https://store.steampowered.com/api/appdetails?appids=10&l=en&cc=us" + if ! http_get "$probe_url" | jq -e '."10".success == true' > /dev/null 2>&1; then + log "Warning: store.steampowered.com probe failed (network or rate-limit). Continuing and handling per-app." + fi } is_known_tool_name() { - local name_lc - name_lc=$(tr '[:upper:]' '[:lower:]' <<<"$1") - if grep -qE 'steam linux runtime|proton|compatibility tool' <<<"$name_lc"; then - return 0 - fi - return 1 + local name_lc + name_lc=$(tr '[:upper:]' '[:lower:]' <<< "$1") + if grep -qE 'steam linux runtime|proton|compatibility tool' <<< "$name_lc"; then + return 0 + fi + return 1 } ensure_cache_dir() { - mkdir -p "$CACHE_DIR" 2>/dev/null || true + mkdir -p "$CACHE_DIR" 2> /dev/null || true } declare -A CACHE_MAP load_cache_map() { - CACHE_MAP=() - if [[ -r "$RESULTS_CACHE" ]]; then - while IFS= read -r raw_line; do - # Normalize historical caches that contain literal "\t" instead of real tabs - local norm_line - norm_line=$(printf "%s" "$raw_line" | sed -E $'s/\\t/\t/g; s/\r$//') - IFS=$'\t' read -r c_score c_appid c_linux c_min c_rec c_name c_pdb <<<"$norm_line" - [[ -z "${c_appid:-}" ]] && continue - c_pdb=${c_pdb:-unknown} - CACHE_MAP["$c_appid"]="$c_score\t$c_appid\t$c_linux\t$c_min\t$c_rec\t$c_name\t$c_pdb" - done < "$RESULTS_CACHE" - fi + CACHE_MAP=() + if [[ -r $RESULTS_CACHE ]]; then + while IFS= read -r raw_line; do + # Normalize historical caches that contain literal "\t" instead of real tabs + local norm_line + norm_line=$(printf "%s" "$raw_line" | sed -E $'s/\\t/\t/g; s/\r$//') + IFS=$'\t' read -r c_score c_appid c_linux c_min c_rec c_name c_pdb <<< "$norm_line" + [[ -z ${c_appid:-} ]] && continue + c_pdb=${c_pdb:-unknown} + CACHE_MAP["$c_appid"]="$c_score\t$c_appid\t$c_linux\t$c_min\t$c_rec\t$c_name\t$c_pdb" + done < "$RESULTS_CACHE" + fi } # --------------------------- ProtonDB integration --------------------------- fetch_protondb_tier() { - local appid="$1" - local url="https://www.protondb.com/api/v1/reports/summaries/${appid}.json" - # Returns minimal JSON including .tier, .confidence; we only need .tier - local tier - tier=$(http_get "$url" | jq -r 'try .tier // "unknown"' 2>/dev/null || true) - if [[ -z "$tier" || "$tier" == "null" ]]; then - echo "unknown" - else - tr '[:upper:]' '[:lower:]' <<<"$tier" | tr -d '\r\n' - fi + local appid="$1" + local url="https://www.protondb.com/api/v1/reports/summaries/${appid}.json" + # Returns minimal JSON including .tier, .confidence; we only need .tier + local tier + tier=$(http_get "$url" | jq -r 'try .tier // "unknown"' 2> /dev/null || true) + if [[ -z $tier || $tier == "null" ]]; then + echo "unknown" + else + tr '[:upper:]' '[:lower:]' <<< "$tier" | tr -d '\r\n' + fi } protondb_allowed() { - local tier="$(tr '[:upper:]' '[:lower:]' <<<"${1:-}")" - case "$tier" in - platinum|native|gold|silver|unknown|"") return 0 ;; - bronze|pending|borked|unsupported|broken) return 1 ;; - *) return 0 ;; - esac + local tier + tier=$(tr '[:upper:]' '[:lower:]' <<< "${1:-}") + case "$tier" in + platinum | native | gold | silver | unknown | "") return 0 ;; + bronze | pending | borked | unsupported | broken) return 1 ;; + *) return 0 ;; + esac } main() { - parse_args "$@" - detect_system - log "System: CPU=[$SYSTEM_CPU_MODEL] class=$SYSTEM_CPU_CLASS | GPU=$SYSTEM_GPU_VENDOR | RAM=${SYSTEM_RAM_GB}GB | Arch=$SYSTEM_ARCH" + parse_args "$@" + detect_system + log "System: CPU=[$SYSTEM_CPU_MODEL] class=$SYSTEM_CPU_CLASS | GPU=$SYSTEM_GPU_VENDOR | RAM=${SYSTEM_RAM_GB}GB | Arch=$SYSTEM_ARCH" - local tmpdir - tmpdir=$(mktemp -d) - trap '[[ -n "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT + local tmpdir + tmpdir=$(mktemp -d) + trap '[[ -n "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT - local games_tsv="$tmpdir/games.tsv" - : > "$games_tsv" + local games_tsv="$tmpdir/games.tsv" + : > "$games_tsv" - # Ensure credentials exist: load from env/config or prompt, else exit - if ! load_credentials; then - prompt_for_credentials - fi + # Ensure credentials exist: load from env/config or prompt, else exit + if ! load_credentials; then + prompt_for_credentials + fi - # Fail fast if we cannot reach the store API to avoid noisy per-app errors - check_network_or_exit + # Fail fast if we cannot reach the store API to avoid noisy per-app errors + check_network_or_exit - if list_owned_games_via_api > "$games_tsv" 2>/dev/null; then - log "Fetched owned games via Steam Web API" - fi + if list_owned_games_via_api > "$games_tsv" 2> /dev/null; then + log "Fetched owned games via Steam Web API" + fi - if [[ ! -s "$games_tsv" ]]; then - die "No games found from Steam Web API. Check STEAM_API_KEY/STEAM_ID64 and network connectivity." - fi + if [[ ! -s $games_tsv ]]; then + die "No games found from Steam Web API. Check STEAM_API_KEY/STEAM_ID64 and network connectivity." + fi - # Fail fast if we cannot reach the store API to avoid noisy per-app errors - check_network_or_exit + # Fail fast if we cannot reach the store API to avoid noisy per-app errors + check_network_or_exit - ensure_cache_dir - if [[ $CLEAR_CACHE -eq 1 ]] && [[ -f "$RESULTS_CACHE" ]]; then - rm -f "$RESULTS_CACHE" || true - log "Cleared cache: $RESULTS_CACHE" - fi - if [[ $FORCE_REFRESH -eq 0 ]]; then - load_cache_map - else - CACHE_MAP=() - fi + ensure_cache_dir + if [[ $CLEAR_CACHE -eq 1 ]] && [[ -f $RESULTS_CACHE ]]; then + rm -f "$RESULTS_CACHE" || true + log "Cleared cache: $RESULTS_CACHE" + fi + if [[ $FORCE_REFRESH -eq 0 ]]; then + load_cache_map + else + CACHE_MAP=() + fi - local results_combined="$tmpdir/results.tsv" - : > "$results_combined" + local results_combined="$tmpdir/results.tsv" + : > "$results_combined" - local count=0 - local total - total=$(wc -l < "$games_tsv" | tr -d ' ') - [[ -z "$total" ]] && total=0 - while IFS=$'\t' read -r appid name; do - [[ $ABORT -eq 1 ]] && break - [[ -n "$appid" ]] || continue - if is_known_tool_name "$name"; then - vlog "[$((count+1))/$total] Skipping compatibility tool: $name ($appid)" - continue - fi - # If cached, reuse; else analyze and cache - if [[ $FORCE_REFRESH -eq 0 && -n "${CACHE_MAP[$appid]+isset}" ]]; then - # Normalize to include ProtonDB column if older cache lacked it - IFS=$'\t' read -r c_score c_a c_linux c_min c_rec c_name c_pdb <<<"${CACHE_MAP[$appid]}" - c_pdb=${c_pdb:-unknown} - vlog "[$((count+1))/$total] Cache hit: $name ($appid) | ProtonDB=$c_pdb" - printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$c_score" "$c_a" "$c_linux" "$c_min" "$c_rec" "$c_name" "$c_pdb" >> "$results_combined" - continue - fi - count=$((count + 1)) - log "Analyzing: $name ($appid) [$count/$total]" - local row url - url="https://store.steampowered.com/api/appdetails?appids=${appid}&l=en&cc=us" - vlog "[$count/$total] Fetching store appdetails: $url" - # Be gentle with the store API - sleep 0.1 - row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true) - if [[ -z "$row" ]]; then continue; fi - local status linux windows mac min_txt rec_txt type - IFS=$'\t' read -r status linux windows mac min_txt rec_txt type <<<"$row" - vlog "[$count/$total] Parsed store data: status=$status linux=$linux type=$type" - # Occasionally Steam returns success=false spuriously; retry once - if [[ "$status" != "ok" ]]; then - vlog "[$count/$total] Store status=fail; retrying once..." - sleep 0.3 - row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true) - IFS=$'\t' read -r status linux windows mac min_txt rec_txt type <<<"$row" - vlog "[$count/$total] After retry: status=$status" - fi - # Try filtered endpoint that often bypasses age/region gates - if [[ "$status" != "ok" ]]; then - local url2="https://store.steampowered.com/api/appdetails?appids=${appid}&filters=platforms,linux_requirements,pc_requirements,type&l=en&cc=us" - vlog "[$count/$total] Retrying with filters: $url2" - sleep 0.1 - row=$(http_get "$url2" | extract_requirements_and_platforms_stdin "$appid" || true) - IFS=$'\t' read -r status linux windows mac min_txt rec_txt type <<<"$row" - vlog "[$count/$total] Filtered fetch status=$status" - fi - if [[ "$status" != "ok" ]]; then continue; fi - if [[ "$type" != "game" && "$type" != "dlc" && "$type" != "" ]]; then continue; fi - # ProtonDB tier - [[ $ABORT -eq 1 ]] && break - local pdb_tier - vlog "[$count/$total] Fetching ProtonDB tier for appid=$appid" - pdb_tier=$(fetch_protondb_tier "$appid") - vlog "[$count/$total] ProtonDB tier=$pdb_tier" + local count=0 + local total + total=$(wc -l < "$games_tsv" | tr -d ' ') + [[ -z $total ]] && total=0 + while IFS=$'\t' read -r appid name; do + [[ $ABORT -eq 1 ]] && break + [[ -n $appid ]] || continue + if is_known_tool_name "$name"; then + vlog "[$((count + 1))/$total] Skipping compatibility tool: $name ($appid)" + continue + fi + # If cached, reuse; else analyze and cache + if [[ $FORCE_REFRESH -eq 0 && -n ${CACHE_MAP[$appid]+isset} ]]; then + # Normalize to include ProtonDB column if older cache lacked it + IFS=$'\t' read -r c_score c_a c_linux c_min c_rec c_name c_pdb <<< "${CACHE_MAP[$appid]}" + c_pdb=${c_pdb:-unknown} + vlog "[$((count + 1))/$total] Cache hit: $name ($appid) | ProtonDB=$c_pdb" + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$c_score" "$c_a" "$c_linux" "$c_min" "$c_rec" "$c_name" "$c_pdb" >> "$results_combined" + continue + fi + count=$((count + 1)) + log "Analyzing: $name ($appid) [$count/$total]" + local row url + url="https://store.steampowered.com/api/appdetails?appids=${appid}&l=en&cc=us" + vlog "[$count/$total] Fetching store appdetails: $url" + # Be gentle with the store API + sleep 0.1 + row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true) + if [[ -z $row ]]; then continue; fi + local status linux _windows _mac min_txt rec_txt type + IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row" + vlog "[$count/$total] Parsed store data: status=$status linux=$linux type=$type" + # Occasionally Steam returns success=false spuriously; retry once + if [[ $status != "ok" ]]; then + vlog "[$count/$total] Store status=fail; retrying once..." + sleep 0.3 + row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true) + IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row" + vlog "[$count/$total] After retry: status=$status" + fi + # Try filtered endpoint that often bypasses age/region gates + if [[ $status != "ok" ]]; then + local url2="https://store.steampowered.com/api/appdetails?appids=${appid}&filters=platforms,linux_requirements,pc_requirements,type&l=en&cc=us" + vlog "[$count/$total] Retrying with filters: $url2" + sleep 0.1 + row=$(http_get "$url2" | extract_requirements_and_platforms_stdin "$appid" || true) + IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row" + vlog "[$count/$total] Filtered fetch status=$status" + fi + if [[ $status != "ok" ]]; then continue; fi + if [[ $type != "game" && $type != "dlc" && $type != "" ]]; then continue; fi + # ProtonDB tier + [[ $ABORT -eq 1 ]] && break + local pdb_tier + vlog "[$count/$total] Fetching ProtonDB tier for appid=$appid" + pdb_tier=$(fetch_protondb_tier "$appid") + vlog "[$count/$total] ProtonDB tier=$pdb_tier" - # Compute hardware-based score - local score_line s_score s_min_ram s_rec_ram - score_line=$(score_game "$linux" "$min_txt" "$rec_txt") - IFS=$'\t' read -r s_score s_min_ram s_rec_ram <<<"$score_line" + # Compute hardware-based score + local score_line s_score s_min_ram s_rec_ram + score_line=$(score_game "$linux" "$min_txt" "$rec_txt") + IFS=$'\t' read -r s_score s_min_ram s_rec_ram <<< "$score_line" - # Gate by ProtonDB: if bronze or below -> mark unplayable and force low score - if ! protondb_allowed "$pdb_tier"; then - s_score=-999 - fi + # Gate by ProtonDB: if bronze or below -> mark unplayable and force low score + if ! protondb_allowed "$pdb_tier"; then + s_score=-999 + fi - printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$s_score" "$appid" "$linux" "$s_min_ram" "$s_rec_ram" "$name" "$pdb_tier" >> "$results_combined" - vlog "[$count/$total] Scored and recorded: score=$s_score min=${s_min_ram}G rec=${s_rec_ram}G" - done < "$games_tsv" + printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$s_score" "$appid" "$linux" "$s_min_ram" "$s_rec_ram" "$name" "$pdb_tier" >> "$results_combined" + vlog "[$count/$total] Scored and recorded: score=$s_score min=${s_min_ram}G rec=${s_rec_ram}G" + done < "$games_tsv" - if [[ ! -s "$results_combined" ]]; then - die "No compatible entries parsed from store API." - fi + if [[ ! -s $results_combined ]]; then + die "No compatible entries parsed from store API." + fi - print_header - local rank=0 - sort -t $'\t' -k1,1nr -k6,6 "$results_combined" | while IFS=$'\t' read -r score appid linux min_ram rec_ram name pdb_tier; do - rank=$((rank + 1)) - local display_name="$name" - if ! protondb_allowed "$pdb_tier"; then - display_name="$name [UNPLAYABLE]" - fi - printf "%-5s %-8s %-6s %-6s %-8s %-9s %s\n" "$rank" "$score" "${min_ram}G" "${rec_ram}G" "$linux" "${pdb_tier:-unknown}" "$display_name" - done + print_header + local rank=0 + sort -t $'\t' -k1,1nr -k6,6 "$results_combined" | while IFS=$'\t' read -r score appid linux min_ram rec_ram name pdb_tier; do + rank=$((rank + 1)) + local display_name="$name" + if ! protondb_allowed "$pdb_tier"; then + display_name="$name [UNPLAYABLE]" + fi + printf "%-5s %-8s %-6s %-6s %-8s %-9s %s\n" "$rank" "$score" "${min_ram}G" "${rec_ram}G" "$linux" "${pdb_tier:-unknown}" "$display_name" + done - # Persist updated results for future runs (only current library entries) - cp -f "$results_combined" "$RESULTS_CACHE" 2>/dev/null || cat "$results_combined" > "$RESULTS_CACHE" + # Persist updated results for future runs (only current library entries) + cp -f "$results_combined" "$RESULTS_CACHE" 2> /dev/null || cat "$results_combined" > "$RESULTS_CACHE" } main "$@" - diff --git a/scripts/utils/toggle_mic.sh b/scripts/utils/toggle_mic.sh index b824dfb..0c16b91 100755 --- a/scripts/utils/toggle_mic.sh +++ b/scripts/utils/toggle_mic.sh @@ -2,47 +2,47 @@ # Check if amixer is installed, if not, install it if ! command -v amixer &> /dev/null; then - echo "amixer could not be found, installing..." - sudo pacman -S --noconfirm alsa-utils + echo "amixer could not be found, installing..." + sudo pacman -S --noconfirm alsa-utils fi # Ensure dbus is running if ! pgrep -x "dbus-daemon" > /dev/null; then - echo "Starting dbus..." - sudo systemctl start dbus + echo "Starting dbus..." + sudo systemctl start dbus fi # Ensure dbus is properly initialized for the user session -export $(dbus-launch) +eval "$(dbus-launch)" # Ensure notification-daemon is installed if ! pacman -Qs notification-daemon > /dev/null; then - echo "Installing notification-daemon..." - sudo pacman -S --noconfirm notification-daemon + echo "Installing notification-daemon..." + sudo pacman -S --noconfirm notification-daemon fi # Ensure dunst is installed and running if ! pacman -Qs dunst > /dev/null; then - echo "Installing dunst..." - sudo pacman -S --noconfirm dunst + echo "Installing dunst..." + sudo pacman -S --noconfirm dunst fi if ! pgrep -x "dunst" > /dev/null; then - echo "Starting dunst..." - dunst & + echo "Starting dunst..." + dunst & fi # Get the current state of the microphone MIC_STATE=$(amixer get Capture | grep '\[on\]') if [ -z "$MIC_STATE" ]; then - # If the microphone is off, turn it on - amixer set Capture cap - sleep 1 # Add a delay to ensure notify-send works correctly - notify-send "Microphone" "Microphone is now ON" + # If the microphone is off, turn it on + amixer set Capture cap + sleep 1 # Add a delay to ensure notify-send works correctly + notify-send "Microphone" "Microphone is now ON" else - # If the microphone is on, turn it off - amixer set Capture nocap - sleep 1 # Add a delay to ensure notify-send works correctly - notify-send "Microphone" "Microphone is now OFF" + # If the microphone is on, turn it off + amixer set Capture nocap + sleep 1 # Add a delay to ensure notify-send works correctly + notify-send "Microphone" "Microphone is now OFF" fi diff --git a/scripts/utils/toggle_wheel.sh b/scripts/utils/toggle_wheel.sh index 96e586b..cc77e27 100755 --- a/scripts/utils/toggle_wheel.sh +++ b/scripts/utils/toggle_wheel.sh @@ -10,48 +10,48 @@ ACTION=$1 # Check if script is run as root if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root. Please run with sudo." - exit 1 + echo "This script must be run as root. Please run with sudo." + exit 1 fi # Check if action parameter is provided -if [[ "$ACTION" != "on" && "$ACTION" != "off" ]]; then - echo "Usage: $0 [on|off]" - exit 1 +if [[ $ACTION != "on" && $ACTION != "off" ]]; then + echo "Usage: $0 [on|off]" + exit 1 fi DEVICE_PATH="" # Find the device path in sysfs (robust scan) for d in /sys/bus/usb/devices/*; do - if [[ -f "$d/idVendor" && -f "$d/idProduct" ]]; then - v=$(cat "$d/idVendor") - p=$(cat "$d/idProduct") - if [[ "$v" == "$VENDOR_ID" && "$p" == "$PRODUCT_ID" ]]; then - DEVICE_PATH="$d" - break - fi + if [[ -f "$d/idVendor" && -f "$d/idProduct" ]]; then + v=$(cat "$d/idVendor") + p=$(cat "$d/idProduct") + if [[ $v == "$VENDOR_ID" && $p == "$PRODUCT_ID" ]]; then + DEVICE_PATH="$d" + break fi + fi done # Check if device was found if [ -z "$DEVICE_PATH" ]; then - echo "Device with Vendor ID $VENDOR_ID and Product ID $PRODUCT_ID not found in /sys/bus/usb/devices." - echo "Tip: Run 'lsusb | grep ${VENDOR_ID}:${PRODUCT_ID}' to verify it's connected." - exit 1 + echo "Device with Vendor ID $VENDOR_ID and Product ID $PRODUCT_ID not found in /sys/bus/usb/devices." + echo "Tip: Run 'lsusb | grep ${VENDOR_ID}:${PRODUCT_ID}' to verify it's connected." + exit 1 fi # Enable or disable the device if [ ! -e "$DEVICE_PATH/authorized" ]; then - echo "The 'authorized' attribute is not present at $DEVICE_PATH." - echo "This device may not support toggling via 'authorized'." - exit 1 + echo "The 'authorized' attribute is not present at $DEVICE_PATH." + echo "This device may not support toggling via 'authorized'." + exit 1 fi if [ "$ACTION" == "off" ]; then - echo '0' > "$DEVICE_PATH/authorized" - echo "Device at $(basename "$DEVICE_PATH") turned off." + echo '0' > "$DEVICE_PATH/authorized" + echo "Device at $(basename "$DEVICE_PATH") turned off." elif [ "$ACTION" == "on" ]; then - echo '1' > "$DEVICE_PATH/authorized" - echo "Device at $(basename "$DEVICE_PATH") turned on." -fi \ No newline at end of file + echo '1' > "$DEVICE_PATH/authorized" + echo "Device at $(basename "$DEVICE_PATH") turned on." +fi diff --git a/scripts/utils/toggle_window_manager.sh b/scripts/utils/toggle_window_manager.sh index 748b7fb..5b8c35a 100755 --- a/scripts/utils/toggle_window_manager.sh +++ b/scripts/utils/toggle_window_manager.sh @@ -5,58 +5,61 @@ set -euo pipefail # Configuration ----------------------------------------------------------------- TARGET_SESSION_NAME="Xfce Session" TARGET_PACKAGES=( - xfwm4 # Compositing window manager with XFCE integration - xfce4-session # Provides the Xfce session entry for display managers - xfce4-panel # Panel with system tray support - xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.) - xfce4-terminal # Handy default terminal for the new environment + xfwm4 # Compositing window manager with XFCE integration + xfce4-session # Provides the Xfce session entry for display managers + xfce4-panel # Panel with system tray support + xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.) + xfce4-terminal # Handy default terminal for the new environment ) # Utility functions -------------------------------------------------------------- info() { echo "[INFO] $*"; } warn() { echo "[WARN] $*" >&2; } -error() { echo "[ERROR] $*" >&2; exit 1; } +error() { + echo "[ERROR] $*" >&2 + exit 1 +} require_command() { - local cmd="$1" pkg_hint="${2:-}" - if ! command -v "$cmd" >/dev/null 2>&1; then - if [[ -n "$pkg_hint" ]]; then - warn "Install '$pkg_hint' to obtain the '$cmd' command." - fi - error "Required command '$cmd' not found." - fi + local cmd="$1" pkg_hint="${2:-}" + if ! command -v "$cmd" > /dev/null 2>&1; then + if [[ -n $pkg_hint ]]; then + warn "Install '$pkg_hint' to obtain the '$cmd' command." + fi + error "Required command '$cmd' not found." + fi } ensure_pacman() { - require_command pacman "pacman" - if ! grep -qi "arch" /etc/os-release 2>/dev/null; then - warn "This script was designed for Arch Linux; continuing anyway." - fi + require_command pacman "pacman" + if ! grep -qi "arch" /etc/os-release 2> /dev/null; then + warn "This script was designed for Arch Linux; continuing anyway." + fi } install_packages() { - local missing=() - for pkg in "${TARGET_PACKAGES[@]}"; do - if ! pacman -Qi "$pkg" >/dev/null 2>&1; then - missing+=("$pkg") - fi - done + local missing=() + for pkg in "${TARGET_PACKAGES[@]}"; do + if ! pacman -Qi "$pkg" > /dev/null 2>&1; then + missing+=("$pkg") + fi + done - if [[ ${#missing[@]} -eq 0 ]]; then - info "All target packages are already installed." - return - fi + if [[ ${#missing[@]} -eq 0 ]]; then + info "All target packages are already installed." + return + fi - if ! command -v sudo >/dev/null 2>&1; then - error "sudo is required to install packages. Install sudo or run this script as root." - fi + if ! command -v sudo > /dev/null 2>&1; then + error "sudo is required to install packages. Install sudo or run this script as root." + fi - info "Installing missing packages: ${missing[*]}" - sudo pacman -S --needed --noconfirm "${missing[@]}" + info "Installing missing packages: ${missing[*]}" + sudo pacman -S --needed --noconfirm "${missing[@]}" } print_post_install_tips() { - cat </dev/null 2>&1; then - info "Terminating current session (ID: $session_id) via loginctl." - loginctl terminate-session "$session_id" - return - fi + if [[ -n $session_id ]] && loginctl show-session "$session_id" > /dev/null 2>&1; then + info "Terminating current session (ID: $session_id) via loginctl." + loginctl terminate-session "$session_id" + return + fi - if loginctl list-sessions 2>/dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then - info "Terminating all sessions for user '$USER' via loginctl." - loginctl terminate-user "$USER" - return - fi + if loginctl list-sessions 2> /dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then + info "Terminating all sessions for user '$USER' via loginctl." + loginctl terminate-user "$USER" + return + fi - warn "loginctl could not terminate the session; attempting fallback logout." - pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually." + warn "loginctl could not terminate the session; attempting fallback logout." + pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually." } main() { - ensure_pacman - install_packages - print_post_install_tips + ensure_pacman + install_packages + print_post_install_tips - # Give the user a moment to read the instructions before logging out. - logout_user + # Give the user a moment to read the instructions before logging out. + logout_user } main "$@" diff --git a/scripts/utils/turn_off_auto_idle_screen_shutdown.sh b/scripts/utils/turn_off_auto_idle_screen_shutdown.sh index 309f507..719d69e 100755 --- a/scripts/utils/turn_off_auto_idle_screen_shutdown.sh +++ b/scripts/utils/turn_off_auto_idle_screen_shutdown.sh @@ -22,20 +22,20 @@ set -euo pipefail log() { printf "[idle-off] %s\n" "$*"; } warn() { printf "[idle-off][WARN] %s\n" "$*" >&2; } -has_cmd() { command -v "$1" >/dev/null 2>&1; } +has_cmd() { command -v "$1" > /dev/null 2>&1; } persist_systemd=false watch_controller=false for arg in "${@:-}"; do - case "$arg" in - --persist-systemd) - persist_systemd=true - ;; - --watch-controller) - watch_controller=true - ;; - -h|--help) - cat </dev/null | grep -q '^org\.gnome\.desktop\.session$'; then - log "Applying GNOME settings to disable idle and lock" - # No lock on idle - gsettings set org.gnome.desktop.screensaver lock-enabled false 2>/dev/null || warn "Failed to set GNOME lock-enabled" - # No idle delay (0 = never) - gsettings set org.gnome.desktop.session idle-delay 0 2>/dev/null || warn "Failed to set GNOME idle-delay" - # No automatic suspend on AC or battery - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' 2>/dev/null || true - gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' 2>/dev/null || true - # Optional: ensure screensaver idle-activation-enabled is false (for older setups) - gsettings set org.gnome.desktop.screensaver idle-activation-enabled false 2>/dev/null || true - fi - fi + if has_cmd gsettings; then + # Detect GNOME by presence of GNOME schemas + if gsettings list-schemas 2> /dev/null | grep -q '^org\.gnome\.desktop\.session$'; then + log "Applying GNOME settings to disable idle and lock" + # No lock on idle + gsettings set org.gnome.desktop.screensaver lock-enabled false 2> /dev/null || warn "Failed to set GNOME lock-enabled" + # No idle delay (0 = never) + gsettings set org.gnome.desktop.session idle-delay 0 2> /dev/null || warn "Failed to set GNOME idle-delay" + # No automatic suspend on AC or battery + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' 2> /dev/null || true + gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing' 2> /dev/null || true + # Optional: ensure screensaver idle-activation-enabled is false (for older setups) + gsettings set org.gnome.desktop.screensaver idle-activation-enabled false 2> /dev/null || true + fi + fi } disable_kde_idle() { - # Best-effort: turn off auto-locker; note: Plasma on Wayland still may rely on compositor-level settings - if has_cmd kwriteconfig5; then - log "Disabling KDE Plasma screen auto-lock (kscreenlockerrc)" - kwriteconfig5 --file kscreenlockerrc --group Daemon --key Autolock false 2>/dev/null || true - kwriteconfig5 --file kscreenlockerrc --group Daemon --key LockOnResume false 2>/dev/null || true - kwriteconfig5 --file kscreenlockerrc --group Daemon --key Timeout 0 2>/dev/null || true - fi + # Best-effort: turn off auto-locker; note: Plasma on Wayland still may rely on compositor-level settings + if has_cmd kwriteconfig5; then + log "Disabling KDE Plasma screen auto-lock (kscreenlockerrc)" + kwriteconfig5 --file kscreenlockerrc --group Daemon --key Autolock false 2> /dev/null || true + kwriteconfig5 --file kscreenlockerrc --group Daemon --key LockOnResume false 2> /dev/null || true + kwriteconfig5 --file kscreenlockerrc --group Daemon --key Timeout 0 2> /dev/null || true + fi } disable_sway_idle() { - # Sway commonly uses swayidle for idle actions; killing it prevents screen blanking/locking - if pgrep -x sway >/dev/null 2>&1; then - if pgrep -x swayidle >/dev/null 2>&1; then - log "Killing swayidle to prevent Wayland idle actions" - pkill -x swayidle || true - fi - fi + # Sway commonly uses swayidle for idle actions; killing it prevents screen blanking/locking + if pgrep -x sway > /dev/null 2>&1; then + if pgrep -x swayidle > /dev/null 2>&1; then + log "Killing swayidle to prevent Wayland idle actions" + pkill -x swayidle || true + fi + fi } disable_lock_daemons() { - # Stop common screen lockers/idle helpers if running - local daemons=(xss-lock light-locker xscreensaver gnome-screensaver) - local found=false - for d in "${daemons[@]}"; do - if pgrep -x "$d" >/dev/null 2>&1; then - found=true - log "Stopping ${d}" - pkill -x "$d" || true - fi - done - if [[ "$found" == false ]]; then - log "No known lock daemons running" - fi + # Stop common screen lockers/idle helpers if running + local daemons=(xss-lock light-locker xscreensaver gnome-screensaver) + local found=false + for d in "${daemons[@]}"; do + if pgrep -x "$d" > /dev/null 2>&1; then + found=true + log "Stopping ${d}" + pkill -x "$d" || true + fi + done + if [[ $found == false ]]; then + log "No known lock daemons running" + fi } disable_tty_idle() { - if has_cmd setterm; then - log "Disabling TTY blanking and powersave" - # Apply to the current TTY; also attempt to broadcast to common TTYs - setterm -blank 0 -powersave off -powerdown 0 || true - for tty in /dev/tty{1..12}; do - [[ -e "$tty" ]] || continue - setterm -blank 0 -powersave off -powerdown 0 <"$tty" >/dev/null 2>&1 || true - done - fi + if has_cmd setterm; then + log "Disabling TTY blanking and powersave" + # Apply to the current TTY; also attempt to broadcast to common TTYs + setterm -blank 0 -powersave off -powerdown 0 || true + for tty in /dev/tty{1..12}; do + [[ -e $tty ]] || continue + setterm -blank 0 -powersave off -powerdown 0 < "$tty" > /dev/null 2>&1 || true + done + fi } reset_idle_activity() { - # Trigger activity hints depending on environment - if [[ -n "${DISPLAY:-}" ]]; then - if has_cmd xset; then - xset s reset || true - xset -dpms || true - xset s off || true - xset s noblank || true - fi - if has_cmd xdotool; then - # No-op mousemove to generate X11 activity without visible movement - xdotool mousemove_relative -- 0 0 2>/dev/null || true - fi - fi + # Trigger activity hints depending on environment + if [[ -n ${DISPLAY:-} ]]; then + if has_cmd xset; then + xset s reset || true + xset -dpms || true + xset s off || true + xset s noblank || true + fi + if has_cmd xdotool; then + # No-op mousemove to generate X11 activity without visible movement + xdotool mousemove_relative -- 0 0 2> /dev/null || true + fi + fi } watch_js_device() { - local dev="$1" - log "Watching controller device: $dev" - while :; do - if [[ ! -e "$dev" ]]; then - warn "Device disappeared: $dev" - break - fi - # Joystick API event size is 8 bytes; block until an event arrives - if dd if="$dev" bs=8 count=1 status=none of=/dev/null; then - reset_idle_activity - # Debounce bursts of events - sleep 0.3 - else - # On read error (e.g., permission), backoff - sleep 1 - fi - done + local dev="$1" + log "Watching controller device: $dev" + while :; do + if [[ ! -e $dev ]]; then + warn "Device disappeared: $dev" + break + fi + # Joystick API event size is 8 bytes; block until an event arrives + if dd if="$dev" bs=8 count=1 status=none of=/dev/null; then + reset_idle_activity + # Debounce bursts of events + sleep 0.3 + else + # On read error (e.g., permission), backoff + sleep 1 + fi + done } start_controller_watchers() { - # Attempt to watch all /dev/input/js* devices; rescan periodically for new ones - local seen="" - declare -A pids + # Attempt to watch all /dev/input/js* devices; rescan periodically for new ones + declare -A pids - # Initial permission check - local any_js=false any_readable=false - for dev in /dev/input/js*; do - [[ -e "$dev" ]] || continue - any_js=true - if [[ -r "$dev" ]]; then any_readable=true; fi - done - if [[ "$any_js" == true && "$any_readable" == false ]]; then - warn "No read permission to /dev/input/js*; add your user to the 'input' group or create udev rules." - fi + # Initial permission check + local any_js=false any_readable=false + for dev in /dev/input/js*; do + [[ -e $dev ]] || continue + any_js=true + if [[ -r $dev ]]; then any_readable=true; fi + done + if [[ $any_js == true && $any_readable == false ]]; then + warn "No read permission to /dev/input/js*; add your user to the 'input' group or create udev rules." + fi - while :; do - local found_any=false - for dev in /dev/input/js*; do - [[ -e "$dev" ]] || continue - found_any=true - if [[ -z "${pids[$dev]:-}" ]] || ! kill -0 "${pids[$dev]}" 2>/dev/null; then - # Start a watcher for this device in background - watch_js_device "$dev" & - pids[$dev]=$! - fi - done - if [[ "$found_any" == false ]]; then - # No joystick devices; quiet rescan - sleep 5 - else - # Rescan less frequently when active - sleep 2 - fi - done + while :; do + local found_any=false + for dev in /dev/input/js*; do + [[ -e $dev ]] || continue + found_any=true + if [[ -z ${pids[$dev]:-} ]] || ! kill -0 "${pids[$dev]}" 2> /dev/null; then + # Start a watcher for this device in background + watch_js_device "$dev" & + pids[$dev]=$! + fi + done + if [[ $found_any == false ]]; then + # No joystick devices; quiet rescan + sleep 5 + else + # Rescan less frequently when active + sleep 2 + fi + done } persist_with_systemd_logind() { - # Set IdleAction=ignore in /etc/systemd/logind.conf and restart logind - # Warning: restarting logind can affect user sessions (e.g., inhibit handling). Use with care. - if [[ "$persist_systemd" != true ]]; then - return 0 - fi - if ! has_cmd sudo; then - warn "sudo not found; cannot persist systemd-logind setting" - return 0 - fi - log "Persisting: setting systemd-logind IdleAction=ignore (requires sudo)" - sudo sh -c ' + # Set IdleAction=ignore in /etc/systemd/logind.conf and restart logind + # Warning: restarting logind can affect user sessions (e.g., inhibit handling). Use with care. + if [[ $persist_systemd != true ]]; then + return 0 + fi + if ! has_cmd sudo; then + warn "sudo not found; cannot persist systemd-logind setting" + return 0 + fi + log "Persisting: setting systemd-logind IdleAction=ignore (requires sudo)" + sudo sh -c ' set -e conf=/etc/systemd/logind.conf if [ ! -f "$conf" ]; then @@ -235,39 +234,38 @@ persist_with_systemd_logind() { printf "\nIdleAction=ignore\n" >> "$conf" fi ' - log "Restarting systemd-logind to apply changes (may briefly affect session inhibitors)" - sudo systemctl restart systemd-logind || warn "Failed to restart systemd-logind" + log "Restarting systemd-logind to apply changes (may briefly affect session inhibitors)" + sudo systemctl restart systemd-logind || warn "Failed to restart systemd-logind" } main() { - log "Starting idle/lock disablement" + log "Starting idle/lock disablement" - # Environment-aware steps - disable_x11_idle - disable_gnome_idle - disable_kde_idle - disable_sway_idle + # Environment-aware steps + disable_x11_idle + disable_gnome_idle + disable_kde_idle + disable_sway_idle - # Generic steps - disable_lock_daemons - disable_tty_idle + # Generic steps + disable_lock_daemons + disable_tty_idle - # Optional persistence - persist_with_systemd_logind + # Optional persistence + persist_with_systemd_logind - if [[ "$watch_controller" == true ]]; then - log "Controller activity watcher enabled" - # Keep the script alive to watch controllers - start_controller_watchers & - watcher_pid=$! - log "Watcher PID: $watcher_pid" - # Wait indefinitely and forward termination - trap 'log "Stopping controller watcher"; kill "$watcher_pid" 2>/dev/null || true; exit 0' INT TERM - wait "$watcher_pid" - else - log "Done. The screen should no longer blank, lock, or power down automatically." - fi + if [[ $watch_controller == true ]]; then + log "Controller activity watcher enabled" + # Keep the script alive to watch controllers + start_controller_watchers & + watcher_pid=$! + log "Watcher PID: $watcher_pid" + # Wait indefinitely and forward termination + trap 'log "Stopping controller watcher"; kill "$watcher_pid" 2>/dev/null || true; exit 0' INT TERM + wait "$watcher_pid" + else + log "Done. The screen should no longer blank, lock, or power down automatically." + fi } main "$@" -