fix: shellcheck issues

This commit is contained in:
Krzysztof Rudnicki 2025-11-01 15:36:22 +01:00
parent 2c46984c61
commit 795f56023e
54 changed files with 5479 additions and 5231 deletions

View File

@ -2,6 +2,9 @@
# 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"
@ -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"

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,12 @@
#!/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
for _ in 1 2 3; do
paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga
done
#pactl set-sink-volume @DEFAULT_SINK@ -50%
@ -13,27 +15,28 @@ play_error_sound() {
# 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
# shellcheck source=./detect_gpu.sh disable=SC1091
. ./detect_gpu.sh
elif [ -f "./detect_gpu_and_install.sh" ]; then
# 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."
fi
install_from_aur() {
if [ ! -d "$HOME/aur" ]; then
local repo_url pkg_name repo_dir
repo_url="$1"
pkg_name="$2"
mkdir -p "$HOME/aur"
fi
cd "$HOME/aur"
local repo_url=$1
local pkg_name=$2
local repo_dir="$(basename "$repo_url" .git)"
cd "$HOME/aur" || return 1
repo_dir="$(basename "$repo_url" .git)"
if [ ! -d "$repo_dir" ]; then
git clone "$repo_url"
@ -41,15 +44,15 @@ install_from_aur() {
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"
cd "$repo_dir" || return 1
if pacman -Qi "$pkg_name" >/dev/null 2>&1; then
if pacman -Qi "$pkg_name" > /dev/null 2>&1; then
echo "$pkg_name is already installed"
return 0
fi
echo "Cleaning old package artifacts to avoid duplicate -U targets"
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true
echo "Building $pkg_name (clean build)"
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
@ -59,7 +62,7 @@ install_from_aur() {
fi
# 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)
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
@ -73,41 +76,43 @@ install_from_aur() {
}
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
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
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
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
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
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
if install_from_aur "$repo_url" "$pkg_name"; then
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
@ -124,19 +129,19 @@ 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
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)"
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
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
if systemctl is-active reflector.timer > /dev/null 2>&1; then
echo "reflector.timer already active"
else
if ! sudo systemctl start reflector.timer; then
@ -144,12 +149,12 @@ if systemctl list-unit-files | grep -q '^reflector.timer'; then
fi
fi
elif systemctl list-unit-files | grep -q '^reflector.service'; then
if systemctl is-enabled reflector.service >/dev/null 2>&1; 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
if systemctl is-active reflector.service > /dev/null 2>&1; then
echo "reflector.service already running"
else
if ! sudo systemctl start reflector.service; then
@ -159,11 +164,21 @@ elif systemctl list-unit-files | grep -q '^reflector.service'; then
else
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
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
pacman_packages+=("$line")
fi
done < "pacman_packages.txt"
@ -219,7 +234,8 @@ for pkg in "${pacman_packages[@]}"; do
fi
if ! pacman -Qi "$pkg" &> /dev/null; then
if ! echo "${aur_packages[@]}" | grep -q "$pkg"; then
if ! printf '%s
' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then
yes | sudo pacman -Sy --noconfirm "$pkg"
else
echo "$pkg exists in AUR packages, skipping pacman installation"
@ -233,25 +249,28 @@ if ! command -v nvm &> /dev/null; then
else
echo "nvm is already installed"
fi
export NVM_DIR=$HOME/.nvm;
source $NVM_DIR/nvm.sh;
nvm i v18.20.5
nvm install --lts
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
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)
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

View File

@ -12,7 +12,7 @@ log() {
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2
}
if [[ ! -f "$CANONICAL_SOURCE" ]]; then
if [[ ! -f $CANONICAL_SOURCE ]]; then
log "Canonical hosts not found at $CANONICAL_SOURCE; aborting enforcement"
exit 0
fi
@ -26,7 +26,7 @@ else
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"

View File

@ -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

View File

@ -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

View File

@ -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)"

View File

@ -11,12 +11,12 @@ 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
if [[ -z ${REASON// /} ]]; then
echo "Empty reason not allowed. Aborting." >&2
exit 1
fi
@ -35,10 +35,10 @@ for s in hosts-bind-mount.service hosts-guard.path; do
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
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
@ -49,7 +49,7 @@ sha_before=$(sha256sum "$TARGET" | awk '{print $1}')
"$EDITOR_CMD" "$TARGET"
sha_after=$(sha256sum "$TARGET" | awk '{print $1}')
if [[ "$sha_before" == "$sha_after" ]]; then
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

View File

@ -47,9 +47,19 @@ 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 }
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
@ -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

View File

@ -18,7 +18,7 @@ for arg in "$@"; do
--no-flush-dns)
FLUSH_DNS=0
;;
-h|--help)
-h | --help)
echo "Usage: $0 [--flush-dns|--no-flush-dns]"
exit 0
;;
@ -29,7 +29,7 @@ done
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"
@ -41,9 +41,9 @@ 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 ""
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
@ -76,10 +76,9 @@ if fetch_remote_header "$TMP_REMOTE_HEAD"; then
REMOTE_AVAILABLE=1
fi
USE_CACHE=0
NEED_UPDATE=0
if [[ -f "$LOCAL_CACHE" ]]; then
if [[ -f $LOCAL_CACHE ]]; then
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
else
local_epoch=""
@ -87,17 +86,15 @@ 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
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
else
if [[ -f "$LOCAL_CACHE" ]]; then
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
@ -265,7 +262,7 @@ sudo chattr -i /etc/hosts
sudo chattr +a /etc/hosts
# Optionally flush DNS caches
if [[ "$FLUSH_DNS" -eq 1 ]]; then
if [[ $FLUSH_DNS -eq 1 ]]; then
echo "Flushing DNS caches..."
sudo systemd-resolve --flush-caches
sudo systemctl restart NetworkManager.service

View File

@ -5,12 +5,12 @@
# Check if ActivityWatch is installed
check_installed() {
# Check if activitywatch-bin package is installed
if pacman -Qi activitywatch-bin &>/dev/null; then
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
if command -v aw-qt &> /dev/null; then
return 0
fi
@ -20,12 +20,12 @@ check_installed() {
# Check if ActivityWatch is running
check_running() {
# Check for aw-qt process
if pgrep -f "aw-qt" >/dev/null 2>&1; then
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
if pgrep -f "aw-server" > /dev/null 2>&1; then
return 0
fi

View File

@ -12,4 +12,3 @@ if echo "$bluetooth_info" | grep -q "Connected: yes"; then
else
echo " Disconnected"
fi

View File

@ -22,11 +22,11 @@ fi
cpu_color="#FFFFFF" # Default color
# Change color based on CPU load
if [[ "$cpu_load" != "N/A" ]]; then
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
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
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
@ -34,11 +34,11 @@ if [[ "$cpu_load" != "N/A" ]]; then
fi
# Change color based on CPU temperature
if [[ "$cpu_temp" != "N/A" ]]; then
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
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
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

View File

@ -2,12 +2,12 @@
# Function to get NVIDIA GPU metrics
get_nvidia_metrics() {
gpu_temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null)
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)
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
@ -17,7 +17,7 @@ get_nvidia_metrics() {
# Function to get Intel GPU metrics
get_intel_metrics() {
gpu_load=$(cat /sys/class/drm/card0/device/gpu_busy_percent 2>/dev/null)
gpu_load=$(cat /sys/class/drm/card0/device/gpu_busy_percent 2> /dev/null)
if [ -z "$gpu_load" ]; then
gpu_load="N/A"
fi
@ -46,10 +46,10 @@ gpu_load=$(echo "$gpu_metrics" | awk -F', ' '{print $2}' | awk -F': ' '{print $2
gpu_color="#FFFFFF"
# Colors for GPU Load
if [[ "$gpu_load" != "N/A" ]]; then
if (( $(echo "$gpu_load < 50.0" | bc -l) )); then
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
elif (($(echo "$gpu_load < 75.0" | bc -l))); then
gpu_color="#F1FA8C" # Yellow
else
gpu_color="#FF5555" # Red
@ -62,4 +62,3 @@ fi
echo -e "<span color=\"$gpu_color\"> ${gpu_temp}, ${gpu_load}%</span>"
echo
echo "#FFFFFF" # Default color for fallback (ignored if markup is enabled)

View File

@ -4,7 +4,7 @@
temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C')
# Ensure the temperature is a valid number
if [[ ! "$temp" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
if [[ ! $temp =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
echo " MB: N/A"
echo
echo "#FF5555" # Red color for error
@ -12,9 +12,9 @@ if [[ ! "$temp" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
fi
# Define temperature thresholds
if (( $(echo "$temp < 50.0" | bc -l) )); then
if (($(echo "$temp < 50.0" | bc -l))); then
color="#50FA7B" # Green for OK temperature
elif (( $(echo "$temp < 70.0" | bc -l) )); then
elif (($(echo "$temp < 70.0" | bc -l))); then
color="#F1FA8C" # Yellow for warning temperature
else
color="#FF5555" # Red for high temperature
@ -24,4 +24,3 @@ fi
echo "${temp}°C" #  is a thermometer icon
echo
echo $color

View File

@ -2,21 +2,26 @@
# 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")
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
echo "${interfaces[@]}"
}
# Detect all active network interfaces
interfaces=$(detect_interfaces)
mapfile -t interfaces < <(detect_interfaces)
# If no active interfaces are found, exit
if [ -z "$interfaces" ]; then
if [ "${#interfaces[@]}" -eq 0 ]; then
echo "No active network interfaces found"
exit 1
fi
@ -34,16 +39,20 @@ current_time=$(date +%s)
last_time=$current_time
# Iterate over each interface and accumulate RX and TX bytes
for interface in $interfaces; do
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"
read -r last_rx last_tx last_time < "$state_file"
else
last_rx=$rx_now
last_tx=$tx_now
@ -62,17 +71,17 @@ done
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
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"

View File

@ -4,8 +4,9 @@
# 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
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
@ -14,8 +15,9 @@ is_monitored_day() {
# 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))
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
@ -25,17 +27,19 @@ is_current_time_in_window() {
# 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)
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
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))
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

View File

@ -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
color="#FF5555"
else
icon="🔊" # Volume icon
color="#50FA7B" # Green
fi
# Output the volume with icon and color
echo "$icon $volume%"
echo
echo $color
echo "$color"

View File

@ -7,7 +7,7 @@ if ! command -v warp-cli &> /dev/null; then
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

View File

@ -10,14 +10,14 @@ if [ -z "$wifi_interface" ]; then
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

View File

@ -8,7 +8,7 @@ is_ubuntu() {
# 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)
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

View File

@ -15,14 +15,14 @@ warn() { printf "\033[1;33m[WARN]\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 <<EOF
cat << EOF
${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub
Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox]
@ -46,15 +46,26 @@ AUTO_FIREFOX=0
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
VERSION="$2"; shift 2;;
VERSION="$2"
shift 2
;;
--force)
FORCE=1; shift;;
FORCE=1
shift
;;
--install-firefox)
AUTO_FIREFOX=1; shift;;
-h|--help)
usage; exit 0;;
AUTO_FIREFOX=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
err "Unknown argument: $1"; usage; exit 2;;
err "Unknown argument: $1"
usage
exit 2
;;
esac
done
@ -65,38 +76,44 @@ require_cmd tar
require_cmd find
require_cmd sed
require_cmd awk
if ! command -v jq >/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
@ -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,7 +157,7 @@ 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
@ -148,19 +168,19 @@ EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
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" <<WRAP
cat > "$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" <<DESK
cat > "$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 <<FF
cat << FF
Options:
1) Install from Mozilla Add-ons (recommended):
https://addons.mozilla.org/firefox/addon/leechblock-ng/
@ -272,13 +294,13 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
# Determine policy directories for detected Firefox-like browsers
declare -a POLICY_DIRS
POLICY_DIRS=()
if command -v firefox >/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" <<JSON
cat > "$tmp_pol" << JSON
{
"policies": {
"ExtensionSettings": {
@ -325,7 +348,7 @@ JSON
fi
else
info "Creating new policies.json at $pol_target"
cat > "$tmp_pol" <<JSON
cat > "$tmp_pol" << JSON
{
"policies": {
"ExtensionSettings": {

View File

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

View File

@ -27,25 +27,25 @@ load_policy_lists() {
local blocked_file="$script_dir/pacman_blocked_keywords.txt"
local whitelist_file="$script_dir/pacman_whitelist.txt"
if [[ -f "$blocked_file" ]]; then
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
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],,}"
BLOCKED_KEYWORDS_LIST[i]="${BLOCKED_KEYWORDS_LIST[i],,}"
done
for i in "${!WHITELISTED_NAMES_LIST[@]}"; do
WHITELISTED_NAMES_LIST[$i]="${WHITELISTED_NAMES_LIST[$i],,}"
WHITELISTED_NAMES_LIST[i]="${WHITELISTED_NAMES_LIST[i],,}"
done
POLICY_LISTS_LOADED=1
@ -56,8 +56,9 @@ needs_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 ;;
-S* | -U | -R | --sync | --upgrade | --remove)
return 0
;;
esac
done
return 1
@ -66,7 +67,7 @@ needs_unlock() {
# 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
if [[ -x $pre ]]; then
echo -e "${CYAN}[hosts-guard] Preparing /etc/hosts for transaction...${NC}" >&2
/bin/bash "$pre" || true
fi
@ -74,17 +75,16 @@ pre_unlock_hosts() {
post_relock_hosts() {
local post="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh"
if [[ -x "$post" ]]; then
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
if ! command -v systemctl > /dev/null 2>&1; then
return 0
fi
@ -120,7 +120,7 @@ ensure_periodic_maintenance() {
setup_script="$HOME/linux-configuration/scripts/setup_periodic_system.sh"
fi
if [[ -n "$setup_script" ]]; then
if [[ -n $setup_script ]]; then
if [[ $EUID -ne 0 ]]; then
sudo bash "$setup_script"
else
@ -147,22 +147,22 @@ function show_help() {
# Function to display a message before executing
function display_operation() {
case "$1" in
-S|-Sy|-S\ *)
-S | -Sy | -S\ *)
echo -e "${BLUE}Installing packages...${NC}" >&2
;;
-Syu|-Syyu)
-Syu | -Syyu)
echo -e "${BLUE}Updating system...${NC}" >&2
;;
-R|-Rs|-Rns|-R\ *)
-R | -Rs | -Rns | -R\ *)
echo -e "${YELLOW}Removing packages...${NC}" >&2
;;
-Ss|-Ss\ *)
-Ss | -Ss\ *)
echo -e "${CYAN}Searching for packages...${NC}" >&2
;;
-Q|-Qs|-Qi|-Ql|-Q\ *)
-Q | -Qs | -Qi | -Ql | -Q\ *)
echo -e "${CYAN}Querying package database...${NC}" >&2
;;
-U|-U\ *)
-U | -U\ *)
echo -e "${BLUE}Installing local packages...${NC}" >&2
;;
-Scc)
@ -180,13 +180,13 @@ function is_blocked_package_name() {
local normalized="${1,,}"
for allowed in "${WHITELISTED_NAMES_LIST[@]}"; do
if [[ "$normalized" == "$allowed" ]]; then
if [[ $normalized == "$allowed" ]]; then
return 1
fi
done
for keyword in "${BLOCKED_KEYWORDS_LIST[@]}"; do
if [[ -n "$keyword" && "$normalized" == *"$keyword"* ]]; then
if [[ -n $keyword && $normalized == *"$keyword"* ]]; then
return 0
fi
done
@ -197,7 +197,7 @@ function is_blocked_package_name() {
# Helper: detect if current invocation includes --noconfirm
function has_noconfirm_flag() {
for arg in "$@"; do
if [[ "$arg" == "--noconfirm" ]]; then
if [[ $arg == "--noconfirm" ]]; then
return 0
fi
done
@ -208,16 +208,16 @@ function has_noconfirm_flag() {
check_and_handle_db_lock() {
local lock_file="/var/lib/pacman/db.lck"
# Quick exit if no lock
if [[ ! -e "$lock_file" ]]; then
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)
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
@ -226,7 +226,7 @@ check_and_handle_db_lock() {
if [[ ${#holders[@]} -gt 0 ]]; then
local -a filtered=()
for pid in "${holders[@]}"; do
[[ "$pid" -eq "$$" ]] && continue
[[ $pid -eq $$ ]] && continue
filtered+=("$pid")
done
holders=("${filtered[@]}")
@ -237,12 +237,12 @@ check_and_handle_db_lock() {
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)
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
if [[ $lower == *" pacman"* || $lower == pacman* || $lower == *"/pacman "* || $lower == *" pamac"* ]]; then
pac_holder=1
elif [[ "$lower" == *packagekit* || "$lower" == *gnome-software* || "$lower" == *discover* ]]; then
elif [[ $lower == *packagekit* || $lower == *gnome-software* || $lower == *discover* ]]; then
gui_holder=1
fi
done
@ -254,21 +254,21 @@ check_and_handle_db_lock() {
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
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
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)
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
@ -279,9 +279,9 @@ check_and_handle_db_lock() {
# Decide whether to remove the lock
local now epoch age
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
if epoch=$(stat -c %Y "$lock_file" 2> /dev/null); then
now=$(date +%s)
age=$(( now - epoch ))
age=$((now - epoch))
else
age=999999
fi
@ -301,7 +301,7 @@ check_and_handle_db_lock() {
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 [[ ${reply,,} == "y" || ${reply,,} == "yes" ]]; then
if [[ $EUID -ne 0 ]]; then
sudo rm -f "$lock_file" || return 1
else
@ -317,7 +317,7 @@ check_and_handle_db_lock() {
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)
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
@ -344,7 +344,7 @@ function remove_installed_blocked_packages() {
# 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
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/)
@ -363,7 +363,7 @@ function check_for_steam() {
local steam_packages=("steam")
# Check if the command is an installation command
if [[ "$1" == "-S" || "$1" == "-Sy" || "$1" == "-Syu" || "$1" == "-Syyu" || "$1" == "-U" ]]; then
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/)
@ -371,8 +371,8 @@ function check_for_steam() {
# Check if argument matches steam
for package in "${steam_packages[@]}"; do
if [[ "$arg" == "$package" || "$arg" == *"/$package-"* || "$arg" == *"/$package/"* ||
"$arg" == *"/$package" || "$package_name" == "$package" ]]; then
if [[ $arg == "$package" || $arg == *"/$package-"* || $arg == *"/$package/"* ||
$arg == *"/$package" || $package_name == "$package" ]]; then
return 0 # Steam package found
fi
done
@ -422,14 +422,14 @@ function prompt_for_steam_challenge() {
# Sleep for random 20-40 seconds
# sleep_duration=$((RANDOM % 20 + 20))
sleep_duration=$((RANDOM % 20))
sleep $sleep_duration
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
if [[ ! -f $words_file ]]; then
echo -e "${RED}Error: words.txt file not found at $words_file${NC}"
return 1
fi
@ -441,7 +441,7 @@ function prompt_for_steam_challenge() {
# 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)
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
@ -455,28 +455,28 @@ function prompt_for_steam_challenge() {
# Convert all words to uppercase
for i in "${!selected_words[@]}"; do
selected_words[$i]=$(echo "${selected_words[$i]}" | tr '[:lower:]' '[:upper:]')
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
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]}"
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
if [[ $scrambled_word == "$target_word" ]]; then
# Use simple reversal as fallback
scrambled_word=$(echo "$target_word" | rev)
fi
@ -508,8 +508,8 @@ function prompt_for_steam_challenge() {
read_status=$?
# Kill the timer display
kill $display_pid 2>/dev/null
wait $display_pid 2>/dev/null
kill "$display_pid" 2> /dev/null
wait "$display_pid" 2> /dev/null
echo # Add a newline after the timer
# Check if read timed out
@ -521,13 +521,13 @@ function prompt_for_steam_challenge() {
# Convert user input to uppercase and trim whitespaces
user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs)
if [[ "$user_input" == "$target_word" ]]; then
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
sleep "$post_challenge_sleep"
return 0
else
@ -537,20 +537,21 @@ function prompt_for_steam_challenge() {
}
# 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
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
if [[ ! -f $words_file ]]; then
echo -e "${RED}Error: words.txt file not found at $words_file${NC}"
return 1
fi
@ -561,7 +562,7 @@ function prompt_for_virtualbox_challenge() {
# 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)
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
@ -575,28 +576,28 @@ function prompt_for_virtualbox_challenge() {
# Convert all words to uppercase
for i in "${!selected_words[@]}"; do
selected_words[$i]=$(echo "${selected_words[$i]}" | tr '[:lower:]' '[:upper:]')
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
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]}"
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
if [[ $scrambled_word == "$target_word" ]]; then
# Use simple reversal as fallback
scrambled_word=$(echo "$target_word" | rev)
fi
@ -628,8 +629,8 @@ function prompt_for_virtualbox_challenge() {
read_status=$?
# Kill the timer display
kill $display_pid 2>/dev/null
wait $display_pid 2>/dev/null
kill "$display_pid" 2> /dev/null
wait "$display_pid" 2> /dev/null
echo # Add a newline after the timer
# Check if read timed out
@ -641,12 +642,12 @@ function prompt_for_virtualbox_challenge() {
# Convert user input to uppercase and trim whitespaces
user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs)
if [[ "$user_input" == "$target_word" ]]; then
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
sleep "$post_challenge_sleep"
return 0
else
@ -656,7 +657,7 @@ function prompt_for_virtualbox_challenge() {
}
# Check for wrapper-specific commands
if [[ "$1" == "--help-wrapper" ]]; then
if [[ $1 == "--help-wrapper" ]]; then
show_help
exit 0
fi
@ -675,8 +676,7 @@ fi
# Check for steam (challenge-eligible package)
if check_for_steam "$@"; then
prompt_for_steam_challenge
if [[ $? -ne 0 ]]; then
if ! prompt_for_steam_challenge; then
exit 1
fi
fi
@ -722,11 +722,11 @@ fi
remove_installed_blocked_packages "$@"
# Display some helpful tips depending on the operation
if [[ "$1" == "-S" || "$1" == "-S "* ]] && [ $exit_code -eq 0 ]; then
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
if [[ $1 == "-Syu" || $1 == "-Syyu" ]] && [ $exit_code -eq 0 ]; then
echo -e "${CYAN}Tip:${NC} Consider restarting after major updates."
fi

View File

@ -45,12 +45,11 @@ draw_box() {
# 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)
local day_of_week
day_of_week=$(date +%u)
printf "${BLUE}${CALENDAR} Day Status${NC}\n"
printf "═══════════════\n"
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")
@ -62,15 +61,15 @@ show_day_status() {
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"
printf '%s%s %s (TODAY - MONITORED)%s\n' "$GREEN" "$CHECK" "${days[$i]}" "$NC"
else
printf "${CYAN}${CHECK} ${days[$i]} (monitored)${NC}\n"
printf '%s%s %s (monitored)%s\n' "$CYAN" "$CHECK" "${days[$i]}" "$NC"
fi
else
if [[ $day_of_week -eq $((i + 1)) ]]; then
printf "${GRAY}${days[$i]} (TODAY - not monitored)${NC}\n"
printf '%s○ %s (TODAY - not monitored)%s\n' "$GRAY" "${days[$i]}" "$NC"
else
printf "${GRAY}${days[$i]}${NC}\n"
printf '%s○ %s%s\n' "$GRAY" "${days[$i]}" "$NC"
fi
fi
done
@ -80,168 +79,174 @@ show_day_status() {
# 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))
local current_hour current_minute current_hour_num
current_hour=$(date +%H)
current_minute=$(date +%M)
current_hour_num=$((10#$current_hour))
printf "${YELLOW}${CLOCK} Time Window Status${NC}\n"
printf "═══════════════════════\n"
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 "${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"
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: ${WHITE}%02d:%s${NC}\n" $current_hour_num "$current_minute"
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: ${GREEN}${CHECK} Within expected window (5AM-8AM)${NC}\n"
printf 'Status: %s%s Within expected window (5AM-8AM)%s\n' "$GREEN" "$CHECK" "$NC"
else
printf "Status: ${YELLOW}○ Outside expected window${NC}\n"
printf 'Status: %s○ Outside expected window%s\n' "$YELLOW" "$NC"
fi
printf "\n"
printf '\n'
}
# Function to show boot time analysis
show_boot_analysis() {
printf "${PURPLE}${COMPUTER} Boot Time Analysis${NC}\n"
printf "═══════════════════════\n"
printf '%s%s Boot Time Analysis%s\n' "$PURPLE" "$COMPUTER" "$NC"
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)
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: ${WHITE}$boot_time${NC}\n"
printf 'System boot time: %s%s%s\n' "$WHITE" "$boot_time" "$NC"
if [[ "$boot_date" == "$today" ]]; then
printf "Boot date: ${GREEN}${CHECK} Today${NC}\n"
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: ${GREEN}${CHECK} Within expected window (5AM-8AM)${NC}\n"
printf "Status: ${GREEN}${CHECK} COMPLIANT${NC}\n"
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 window: ${RED}${CROSS} Outside expected window${NC}\n"
printf "Status: ${RED}${WARNING} NON-COMPLIANT${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
else
printf "Boot date: ${YELLOW}○ Not today ($boot_date)${NC}\n"
printf "Status: ${YELLOW}○ System was not booted today${NC}\n"
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"
printf '\n'
}
# Function to show monitoring system status
show_system_status() {
printf "${CYAN}${BELL} Monitoring System${NC}\n"
printf "═══════════════════════\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: ${GREEN}${CHECK} ENABLED${NC}\n"
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: ${GREEN}${CHECK} ACTIVE${NC}\n"
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
printf 'Timer: %s%s ACTIVE%s\n' "$GREEN" "$CHECK" "$NC"
else
printf "Timer: ${RED}${CROSS} INACTIVE${NC}\n"
printf 'Timer: %s%s INACTIVE%s\n' "$RED" "$CROSS" "$NC"
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"
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: ${RED}${CROSS} NOT ENABLED${NC}\n"
printf "Timer: ${RED}${CROSS} NOT ACTIVE${NC}\n"
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"
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))
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
if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then
is_monitored=true
fi
printf "${WHITE}"
printf '%s' "$WHITE"
draw_box "COMPLIANCE OVERVIEW"
printf "${NC}\n"
printf '%s\n' "$NC"
if [[ "$is_monitored" == true ]]; then
printf "Today: ${GREEN}${CHECK} Monitored day${NC}\n"
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: ${GREEN}${CHECK} PC is on during expected window${NC}\n"
printf "Action needed: ${GREEN}None - currently compliant${NC}\n"
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
# 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)
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: ${GREEN}${CHECK} PC was booted in expected window${NC}\n"
printf "Action needed: ${GREEN}None - compliant${NC}\n"
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: ${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"
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
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"
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"
printf '\n'
}
# Function to show recent activity
show_recent_activity() {
printf "${GRAY}📋 Recent Activity${NC}\n"
printf "════════════════\n"
printf '%s📋 Recent Activity%s\n' "$GRAY" "$NC"
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")
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 "${GRAY}No recent monitoring activity${NC}\n"
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 "${RED}$line${NC}\n"
printf '%s%s%s\n' "$RED" "$line" "$NC"
elif [[ $line == *"compliance OK"* ]]; then
printf "${GREEN}$line${NC}\n"
printf '%s%s%s\n' "$GREEN" "$line" "$NC"
else
printf "${GRAY}$line${NC}\n"
printf '%s%s%s\n' "$GRAY" "$line" "$NC"
fi
done
fi
printf "\n"
printf '\n'
}
# Main display function
@ -249,12 +254,15 @@ main() {
clear
# Header
printf "${BLUE}"
printf '%s' "$BLUE"
draw_box "PC STARTUP MONITOR - VISUAL STATUS"
printf "${NC}\n\n"
printf '%s\n\n' "$NC"
printf "${WHITE}Current Date/Time: $(date)${NC}\n"
printf "${WHITE}System Uptime: $(uptime -p)${NC}\n\n"
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
@ -265,13 +273,13 @@ main() {
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"
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

View File

@ -14,8 +14,8 @@ UNDO=false
for arg in "$@"; do
case "$arg" in
--undo) UNDO=true ;;
-h|--help)
cat <<EOF
-h | --help)
cat << EOF
Usage: $SCRIPT_NAME [--undo]
Actions:
@ -42,26 +42,26 @@ fi
# Map binaries to a logical product key
declare -A BIN_TO_KEY=(
[thorium-browser]=thorium-browser
[thorium - browser]=thorium-browser
[thorium]=thorium-browser
[chromium]=chromium
[google-chrome]=google-chrome
[google-chrome-stable]=google-chrome
[brave-browser]=brave-browser
[google - chrome]=google-chrome
[google - chrome - stable]=google-chrome
[brave - browser]=brave-browser
[vivaldi]=vivaldi
[vivaldi-stable]=vivaldi
[microsoft-edge-stable]=microsoft-edge-stable
[vivaldi - stable]=vivaldi
[microsoft - edge - stable]=microsoft-edge-stable
[opera]=opera
)
# Candidate policy directories per product key (first existing or first creatable is used)
declare -A CANDIDATE_DIRS=(
[thorium-browser]="/etc/thorium/policies/managed:/etc/opt/thorium/policies/managed:/etc/opt/thorium-browser/policies/managed:/etc/thorium-browser/policies/managed"
[thorium - browser]="/etc/thorium/policies/managed:/etc/opt/thorium/policies/managed:/etc/opt/thorium-browser/policies/managed:/etc/thorium-browser/policies/managed"
[chromium]="/etc/chromium/policies/managed"
[google-chrome]="/etc/opt/chrome/policies/managed"
[brave-browser]="/etc/opt/brave/policies/managed"
[google - chrome]="/etc/opt/chrome/policies/managed"
[brave - browser]="/etc/opt/brave/policies/managed"
[vivaldi]="/etc/opt/vivaldi/policies/managed"
[microsoft-edge-stable]="/etc/opt/edge/policies/managed"
[microsoft - edge - stable]="/etc/opt/edge/policies/managed"
[opera]="/etc/opt/opera/policies/managed"
)
@ -75,7 +75,7 @@ POLICY_JSON='{
# Discover installed browsers
declare -A INSTALLED_KEYS=()
for bin in "${!BIN_TO_KEY[@]}"; do
if command -v "$bin" >/dev/null 2>&1; then
if command -v "$bin" > /dev/null 2>&1; then
key=${BIN_TO_KEY[$bin]}
INSTALLED_KEYS[$key]=1
fi
@ -83,7 +83,7 @@ done
if [[ ${#INSTALLED_KEYS[@]} -eq 0 ]]; then
echo "[warn] No supported Chromium-based browsers detected in PATH. Proceeding to configure Thorium paths anyway."
INSTALLED_KEYS[thorium-browser]=1
INSTALLED_KEYS[thorium - browser]=1
fi
choose_target_dir() {
@ -93,7 +93,7 @@ choose_target_dir() {
read -r -a dirs <<< "${CANDIDATE_DIRS[$key]:-}"
# Prefer an existing directory; else pick the first candidate
for d in "${dirs[@]}"; do
if [[ -d "$d" ]]; then
if [[ -d $d ]]; then
echo "$d"
return 0
fi
@ -102,7 +102,8 @@ choose_target_dir() {
}
apply_policy() {
local target_dir="$1"; shift
local target_dir="$1"
shift
local file="$target_dir/$POLICY_FILENAME"
echo "[apply] $file"
@ -112,16 +113,17 @@ apply_policy() {
local tmp
tmp=$(mktemp)
printf '%s
' "$POLICY_JSON" >"$tmp"
' "$POLICY_JSON" > "$tmp"
install -m 0644 "$tmp" "$file"
rm -f "$tmp"
}
remove_policy() {
local target_dir="$1"; shift
local target_dir="$1"
shift
local file="$target_dir/$POLICY_FILENAME"
if [[ -f "$file" ]]; then
if [[ -f $file ]]; then
echo "[remove] $file"
rm -f -- "$file"
else
@ -133,14 +135,14 @@ 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
if [[ -z ${CANDIDATE_DIRS[$key]:-} ]]; then
echo "[warn] No known policy directories for '$key'; skipping."
continue
fi
target_dir=$(choose_target_dir "$key")
if [[ "$UNDO" == true ]]; then
if [[ $UNDO == true ]]; then
remove_policy "$target_dir"
else
apply_policy "$target_dir"
@ -149,14 +151,13 @@ for key in "${!INSTALLED_KEYS[@]}"; do
changed_any=true
done
if [[ "$changed_any" == false ]]; then
if [[ $changed_any == false ]]; then
echo "[info] Nothing to do."
fi
if [[ "$UNDO" == true ]]; then
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."
fi

View File

@ -33,7 +33,7 @@ check_sudo() {
}
# Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then
if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER"
else
@ -56,41 +56,41 @@ disable_midnight_shutdown() {
local removed_files=()
# Stop and disable timer if it exists
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
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
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
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
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
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
if [[ -f $check_script ]]; then
rm -f "$check_script"
removed_files+=("$check_script")
echo "✓ Removed check script: $check_script"
@ -130,8 +130,6 @@ show_current_status() {
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
@ -142,14 +140,12 @@ show_current_status() {
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"
@ -159,13 +155,13 @@ show_current_status() {
# Check systemd status
if $timer_exists; then
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; 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
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"
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
@ -445,13 +441,13 @@ test_setup() {
echo ""
echo "Timer status:"
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
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
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"
@ -459,7 +455,7 @@ test_setup() {
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"
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
@ -511,10 +507,10 @@ confirm_setup() {
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
read -r -p "Do you want to proceed? (y/N): " confirm
case "$confirm" in
[yY]|[yY][eE][sS])
[yY] | [yY][eE][sS])
echo "Proceeding with setup..."
return 0
;;
@ -537,10 +533,10 @@ confirm_disable() {
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
read -r -p "Do you want to proceed with disabling? (y/N): " confirm
case "$confirm" in
[yY]|[yY][eE][sS])
[yY] | [yY][eE][sS])
echo "Proceeding with disable..."
return 0
;;
@ -594,7 +590,7 @@ case "${1:-enable}" in
check_sudo "$@"
show_current_status
;;
"help"|"-h"|"--help")
"help" | "-h" | "--help")
show_usage
;;
*)

View File

@ -11,11 +11,11 @@ INTERACTIVE_MODE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i|--interactive)
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h|--help)
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
@ -34,7 +34,7 @@ echo "PC Startup Time Monitor for Arch Linux"
echo "======================================"
echo "Current Date: $(date)"
echo "User: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
@ -50,7 +50,7 @@ check_sudo() {
}
# Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then
if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER"
else
@ -63,10 +63,11 @@ 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
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
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
@ -75,8 +76,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)) # Convert to decimal to avoid octal issues
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
@ -87,55 +89,61 @@ 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 boot_time=""
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
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")
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
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
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")
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
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
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")
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=$(echo "$boot_time" | cut -d' ' -f1)
if [[ "$boot_date" != "$today" ]]; then
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=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1)
local boot_hour_num=$((10#$boot_hour)) # Convert to decimal
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"
@ -151,9 +159,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 ""
echo "⚠️ PC STARTUP TIME WARNING"
@ -172,12 +181,12 @@ show_startup_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 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
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
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
fi
@ -254,7 +263,8 @@ create_monitoring_script() {
# 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")
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)
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)"
@ -464,13 +480,13 @@ test_setup() {
echo ""
echo "Timer status:"
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
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
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"
@ -523,11 +539,11 @@ confirm_setup() {
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
if [[ $INTERACTIVE_MODE == "true" ]]; then
read -r -p "Do you want to proceed? (y/N): " confirm
case "$confirm" in
[yY]|[yY][eE][sS])
[yY] | [yY][eE][sS])
echo "Proceeding with setup..."
return 0
;;

View File

@ -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 <command> [options]
Commands:
@ -59,7 +59,7 @@ die() {
}
require_non_root() {
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
if [[ ${EUID:-$(id -u)} -eq 0 ]]; then
die "Run this script as a regular desktop user, not root."
fi
}
@ -69,7 +69,7 @@ prompt_yes_no() {
local reply
read -r -p "$prompt [y/N]: " reply
case "$reply" in
[Yy][Ee][Ss]|[Yy]) return 0 ;;
[Yy][Ee][Ss] | [Yy]) return 0 ;;
*) return 1 ;;
esac
}
@ -82,7 +82,7 @@ ensure_directories() {
missing_commands() {
local missing=()
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
if ! command -v "$cmd" > /dev/null 2>&1; then
missing+=("$cmd")
fi
done
@ -90,19 +90,19 @@ missing_commands() {
}
install_dependencies() {
if ! command -v systemctl >/dev/null 2>&1; then
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
if ((${#needed[@]} == 0)); then
log "All required packages (${required[*]}) are present."
return
fi
if command -v pacman >/dev/null 2>&1; then
if command -v pacman > /dev/null 2>&1; then
log "Installing missing packages: ${needed[*]}"
sudo pacman -S --needed --noconfirm "${needed[@]}"
else
@ -112,13 +112,12 @@ install_dependencies() {
create_password_file() {
local force=${1:-0}
if [[ -f "$PASSWORD_FILE" && "$force" -ne 1 ]];
then
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 [[ -f $PASSWORD_FILE ]]; then
if ! prompt_yes_no "Regenerate the stored VNC password?"; then
log "Keeping existing password."
return
@ -128,25 +127,25 @@ create_password_file() {
local password confirm generated=0
read -rsp "Enter VNC password (leave blank to auto-generate): " password
printf '\n'
if [[ -z "$password" ]]; then
if [[ -z $password ]]; then
generated=1
password=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 8)
password=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 8)
log "Generated VNC password: $password"
else
read -rsp "Confirm password: " confirm
printf '\n'
if [[ "$password" != "$confirm" ]]; then
if [[ $password != "$confirm" ]]; then
die "Passwords do not match."
fi
fi
local tmp
tmp=$(mktemp)
x11vnc -storepasswd "$password" "$tmp" >/dev/null
x11vnc -storepasswd "$password" "$tmp" > /dev/null
install -m 600 "$tmp" "$PASSWORD_FILE"
rm -f "$tmp"
if (( generated == 0 )); then
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."
@ -154,10 +153,10 @@ create_password_file() {
}
create_env_file() {
if [[ -f "$ENV_FILE" ]]; then
if [[ -f $ENV_FILE ]]; then
return
fi
cat >"$ENV_FILE" <<EOF
cat > "$ENV_FILE" << EOF
# control-from-mobile configuration
# Adjust these values if needed and rerun: systemctl --user restart $SERVICE_NAME
X11_DISPLAY="$DEFAULT_DISPLAY"
@ -169,7 +168,7 @@ EOF
}
create_runner_script() {
cat >"$RUNNER_FILE" <<'EOF'
cat > "$RUNNER_FILE" << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
@ -213,7 +212,7 @@ EOF
}
create_service_file() {
cat >"$SERVICE_FILE" <<EOF
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=Expose X11 desktop over VNC for Android control
After=graphical-session.target
@ -238,7 +237,7 @@ reload_user_daemon() {
}
ensure_service_present() {
if [[ ! -f "$SERVICE_FILE" || ! -x "$RUNNER_FILE" ]]; then
if [[ ! -f $SERVICE_FILE || ! -x $RUNNER_FILE ]]; then
die "Service files missing. Run: $SCRIPT_NAME setup"
fi
}
@ -273,7 +272,7 @@ disable_service() {
show_info() {
ensure_service_present
# shellcheck disable=SC1090
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
[[ -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}"
@ -290,14 +289,14 @@ show_info() {
log "Password file: $PASSWORD_FILE"
local -a ip_list=()
if command -v hostname >/dev/null 2>&1; then
if command -v hostname > /dev/null 2>&1; then
while IFS= read -r line; do
[[ -z "$line" ]] && continue
[[ -z $line ]] && continue
ip_list+=("$line")
done < <(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true)
done < <(hostname -I 2> /dev/null | tr ' ' '\n' | grep -E '^[0-9]' || true)
fi
if (( ${#ip_list[@]} > 0 )); then
if ((${#ip_list[@]} > 0)); then
log "Detected LAN IPs:"
for ip in "${ip_list[@]}"; do
printf ' - %s\n' "$ip"
@ -313,17 +312,17 @@ show_info() {
printf ' Host: <your-ip>\n Port: %s\n Password: <stored during setup>\n' "$port"
local qr_host
if (( ${#ip_list[@]} > 0 )); then
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
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
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
@ -342,7 +341,7 @@ uninstall_files() {
rm -f "$SERVICE_FILE"
rm -f "$RUNNER_FILE"
rm -f "$ENV_FILE"
if (( purge_password )); then
if ((purge_password)); then
rm -f "$PASSWORD_FILE"
log "Removed password file."
fi
@ -359,7 +358,7 @@ main() {
case "$cmd" in
setup)
local force=0
if [[ "${1:-}" == "--force-password" ]]; then
if [[ ${1:-} == "--force-password" ]]; then
force=1
shift || true
fi
@ -397,13 +396,13 @@ main() {
;;
uninstall)
local purge=0
if [[ "${1:-}" == "--purge" ]]; then
if [[ ${1:-} == "--purge" ]]; then
purge=1
shift || true
fi
uninstall_files "$purge"
;;
help|--help|-h|"" )
help | --help | -h | "")
usage
;;
*)

View File

@ -7,7 +7,7 @@ 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
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
echo "Package installation requires sudo privileges."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
@ -20,7 +20,7 @@ echo "Current Date: $(date)"
echo "User: ${SUDO_USER:-$USER}"
# Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then
if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER"
else
@ -38,7 +38,7 @@ check_activitywatch_installed() {
echo "========================================"
# Check if activitywatch-bin is installed via pacman
if pacman -Qi activitywatch-bin &>/dev/null; then
if pacman -Qi activitywatch-bin &> /dev/null; then
echo "✓ activitywatch-bin package is installed"
return 0
fi
@ -52,7 +52,7 @@ check_activitywatch_installed() {
)
for path in "${common_paths[@]}"; do
if [[ -x "$path" ]]; then
if [[ -x $path ]]; then
echo "✓ ActivityWatch found at: $path"
return 0
fi
@ -78,13 +78,13 @@ install_activitywatch() {
local helper_found=""
for helper in "${aur_helpers[@]}"; do
if command -v "$helper" &>/dev/null; then
if command -v "$helper" &> /dev/null; then
helper_found="$helper"
break
fi
done
if [[ -n "$helper_found" && "$helper_found" != "makepkg" ]]; then
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
@ -110,7 +110,7 @@ install_activitywatch_manual() {
cd "$temp_dir"
# Download PKGBUILD
if command -v git &>/dev/null; then
if command -v git &> /dev/null; then
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
else
echo "Installing git..."
@ -133,13 +133,13 @@ check_activitywatch_running() {
echo "=================================="
# Check for aw-qt process
if pgrep -f "aw-qt" >/dev/null; then
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
if pgrep -f "aw-server" > /dev/null; then
echo "✓ ActivityWatch server is running"
return 0
fi
@ -157,7 +157,7 @@ start_activitywatch() {
# Find aw-qt executable
local aw_qt_path=""
if command -v aw-qt &>/dev/null; then
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"
@ -181,7 +181,7 @@ start_activitywatch() {
# Give it time to start
sleep 3
if check_activitywatch_running >/dev/null 2>&1; then
if check_activitywatch_running > /dev/null 2>&1; then
echo "✓ ActivityWatch started successfully"
else
echo "! ActivityWatch may be starting (check system tray)"
@ -229,21 +229,23 @@ EOF
echo "✓ Created XDG autostart entry: $desktop_file"
# Method 2: i3 config autostart (specific to i3)
if [[ -f "$i3_config" ]]; then
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'"
sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config'
# Auto-start ActivityWatch
exec --no-startup-id aw-qt
EOF"
else
echo "" >> "$i3_config"
echo "# Auto-start ActivityWatch" >> "$i3_config"
echo "exec --no-startup-id aw-qt" >> "$i3_config"
{
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"
@ -350,14 +352,14 @@ test_setup() {
echo "=================="
echo "Installation status:"
if check_activitywatch_installed >/dev/null 2>&1; then
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
if check_activitywatch_running > /dev/null 2>&1; then
echo "✓ ActivityWatch is running"
else
echo "✗ ActivityWatch is not running"
@ -432,7 +434,7 @@ main() {
fi
# Install if needed
if [[ "$need_install" == true ]]; then
if [[ $need_install == true ]]; then
install_activitywatch
fi
@ -442,7 +444,7 @@ main() {
fi
# Start if needed
if [[ "$need_start" == true ]]; then
if [[ $need_start == true ]]; then
start_activitywatch
fi

View File

@ -23,13 +23,13 @@ log_error() {
}
require_root() {
if [[ "${EUID}" -ne 0 ]]; then
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
if ! command -v pacman > /dev/null 2>&1; then
log_error "pacman not found. This script is intended for Arch Linux systems."
fi
}
@ -57,7 +57,7 @@ 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
if pacman -Q "${kernel_pkg}" > /dev/null 2>&1; then
header_pkg="${kernel_pkg}-headers"
headers+=("${header_pkg}")
fi
@ -72,7 +72,7 @@ maybe_remove_conflicting_host_packages() {
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
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
@ -88,7 +88,7 @@ install_packages() {
mapfile -t headers < <(printf '%s\n' "$@" | sort -u)
fi
packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}")
if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then
if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
packages+=("dkms")
fi
if [[ ${#headers[@]} -gt 0 ]]; then
@ -100,8 +100,8 @@ install_packages() {
rebuild_virtualbox_modules() {
local host_package=$1
if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then
if command -v dkms >/dev/null 2>&1; then
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
@ -122,7 +122,7 @@ reload_virtualbox_modules() {
local mod
for mod in "${modules[@]}"; do
if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then
if ! modprobe "${mod}" >/dev/null 2>&1; then
if ! modprobe "${mod}" > /dev/null 2>&1; then
log_warn "Module ${mod} failed to load; check dmesg for details."
fi
fi
@ -137,11 +137,11 @@ reload_virtualbox_modules() {
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
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
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
@ -150,7 +150,7 @@ warn_if_secure_boot_enabled() {
remind_group_membership() {
local invoking_user=${SUDO_USER:-}
if [[ -n "${invoking_user}" && "${invoking_user}" != "root" ]]; then
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
@ -165,7 +165,7 @@ main() {
PACMAN_INSTALL_FLAGS=(--needed)
PACMAN_REMOVE_FLAGS=()
if [[ "${PACMAN_CONFIRM:-0}" == "1" ]]; then
if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then
log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation."
else
PACMAN_INSTALL_FLAGS+=(--noconfirm)
@ -179,7 +179,7 @@ main() {
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
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

View File

@ -11,11 +11,11 @@ INTERACTIVE_MODE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i|--interactive)
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h|--help)
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
@ -47,7 +47,7 @@ echo "=================================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
@ -79,7 +79,7 @@ echo "✓ Configuration written to: $CONFIG_FILE"
# Function to backup file if it exists
backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
if [[ -f $file ]]; then
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
echo "✓ Backed up $file"
fi
@ -122,15 +122,19 @@ configure_gcc_workaround() {
echo "3. Configuring GCC Mismatch Workaround..."
echo "=========================================="
PROFILE_FILE="/etc/profile"
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
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"
{
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"
@ -152,7 +156,7 @@ install_pyroveil() {
local install_pyroveil=true
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
if [[ $INTERACTIVE_MODE == "true" ]]; then
read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
@ -162,13 +166,13 @@ install_pyroveil() {
echo "Auto-installing Pyroveil (use --interactive to prompt)"
fi
if [[ "$install_pyroveil" == "true" ]]; then
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)
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
@ -182,7 +186,7 @@ install_pyroveil() {
# Clone and build pyroveil as the original user
echo "Installing Pyroveil to $pyroveil_dir..."
if [[ -d "$pyroveil_dir" ]]; then
if [[ -d $pyroveil_dir ]]; then
echo "Pyroveil directory already exists. Updating..."
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
else
@ -275,7 +279,8 @@ suggest_kernel_params() {
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)
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
@ -311,7 +316,7 @@ suggest_desktop_settings() {
echo " System Settings → Effects → Enable desktop effects"
# Detect current desktop environment
if [[ -n "$XDG_CURRENT_DESKTOP" ]]; then
if [[ -n $XDG_CURRENT_DESKTOP ]]; then
echo ""
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
fi

View File

@ -25,10 +25,6 @@ INSTALL_ONLY="false"
LIST_ONLY="false"
VERBOSE="false"
log() {
printf '%s\n' "$*"
}
log_info() {
printf '\033[1;34m[INFO]\033[0m %s\n' "$*"
}
@ -42,7 +38,7 @@ log_error() {
}
usage() {
cat <<EOF
cat << EOF
Usage: $(basename "$0") [options]
Options:
@ -63,41 +59,57 @@ EOF
while [[ $# -gt 0 ]]; do
case "$1" in
--path)
ROOT_DIR="$2"; shift 2 ;;
ROOT_DIR="$2"
shift 2
;;
--skip-install)
SKIP_INSTALL="true"; shift ;;
SKIP_INSTALL="true"
shift
;;
--install-only)
INSTALL_ONLY="true"; shift ;;
INSTALL_ONLY="true"
shift
;;
--list-only)
LIST_ONLY="true"; shift ;;
LIST_ONLY="true"
shift
;;
--verbose)
VERBOSE="true"; shift ;;
-h|--help)
usage; exit 0 ;;
VERBOSE="true"
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown argument: $1"; usage; exit 2 ;;
log_error "Unknown argument: $1"
usage
exit 2
;;
esac
done
if [[ ! -d "$ROOT_DIR" ]]; then
if [[ ! -d $ROOT_DIR ]]; then
log_error "Path not found: $ROOT_DIR"
exit 2
fi
is_cmd() { command -v "$1" >/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"
pkg="$1"
cmd="$2"
if is_cmd "$cmd"; then
[[ "$VERBOSE" == "true" ]] && log_info "Found $cmd"
[[ $VERBOSE == "true" ]] && log_info "Found $cmd"
return 0
fi
if [[ "$SKIP_INSTALL" == "true" ]]; then
if [[ $SKIP_INSTALL == "true" ]]; then
log_warn "Skipping install of $pkg ($cmd not found)"
return 1
fi
@ -126,7 +138,7 @@ install_linters() {
# 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 ! 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
@ -162,8 +174,7 @@ install_linters() {
}
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"
@ -173,14 +184,14 @@ discover_shell_files() {
local -a all
all=()
if git -C "$base" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
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}/}"
f="${f#"${base}"/}"
all+=("$f")
done < <(find "$base" -type f -print0)
fi
@ -191,37 +202,38 @@ discover_shell_files() {
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 ;;
*.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
[[ -f $abs && -r $abs ]] || continue
if [[ "$rel" == *.sh || "$rel" == *.bash || "$rel" == *.zsh ]]; then
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
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
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"
: > "$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"
@ -232,7 +244,7 @@ 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
if [[ $VERBOSE == "true" ]]; then
tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /'
fi
}
@ -241,7 +253,7 @@ run_linters() {
local issues=0
local count
count=$(tr -cd '\0' < "$ABS_FILES_Z" | wc -c)
if [[ "$count" -eq 0 ]]; then
if [[ $count -eq 0 ]]; then
log_warn "No shell files found to lint."
return 0
fi
@ -251,8 +263,8 @@ run_linters() {
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))
if ! shellcheck -x -S style "${FILES[@]}" > "$sc_out" 2>&1; then
issues=$((issues + 1))
fi
else
log_warn "shellcheck not found; skipping"
@ -261,9 +273,9 @@ run_linters() {
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
if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" > "$shfmt_out" 2>&1; then
# shfmt returns non-zero when diff exists
issues=$((issues+1))
issues=$((issues + 1))
fi
else
log_warn "shfmt not found; skipping"
@ -271,10 +283,15 @@ run_linters() {
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
if ! checkbashisms "${FILES[@]}" >"$cbi_out" 2>&1; then
issues=$((issues+1))
# 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"
@ -287,13 +304,15 @@ run_linters() {
# Partition files by shebang for better accuracy
local -a BASH_FILES ZSH_FILES SH_FILES
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
first=$(head -n 1 -- "$f" 2> /dev/null || true)
if [[ $first =~ bash ]]; then
BASH_FILES+=("$f")
elif [[ "$first" =~ zsh ]]; then
elif [[ $first =~ zsh ]]; then
ZSH_FILES+=("$f")
else
SH_FILES+=("$f")
@ -301,24 +320,24 @@ run_linters() {
done
if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then
if ! bash -n "${BASH_FILES[@]}" 2>"$bash_out"; then
issues=$((issues+1))
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))
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))
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))
if ! sh -n "${SH_FILES[@]}" 2> "$sh_out"; then
issues=$((issues + 1))
fi
fi
fi
@ -326,42 +345,42 @@ run_linters() {
echo
log_info "========== Shell Lint Report =========="
if [[ -s "$sc_out" ]]; then
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
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
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
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
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
if [[ -s $sh_out ]]; then
printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n'
cat "$sh_out"
else
@ -379,23 +398,22 @@ run_linters() {
}
# Main
if [[ "$INSTALL_ONLY" == "true" ]]; then
if [[ $INSTALL_ONLY == "true" ]]; then
install_linters
exit $?
fi
# Only attempt installs if not list-only
if [[ "$LIST_ONLY" != "true" ]]; then
if [[ $LIST_ONLY != "true" ]]; then
install_linters || true
fi
discover_shell_files "$ROOT_DIR"
print_file_list
if [[ "$LIST_ONLY" == "true" ]]; then
if [[ $LIST_ONLY == "true" ]]; then
exit 0
fi
run_linters
exit $?

View File

@ -11,11 +11,11 @@ INTERACTIVE_MODE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i|--interactive)
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h|--help)
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
@ -47,7 +47,7 @@ echo "==================================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
@ -92,15 +92,15 @@ verify_files() {
local missing_files=()
if [[ ! -f "$PACMAN_WRAPPER_SCRIPT" ]]; then
if [[ ! -f $PACMAN_WRAPPER_SCRIPT ]]; then
missing_files+=("$PACMAN_WRAPPER_SCRIPT")
fi
if [[ ! -f "$PACMAN_WRAPPER_INSTALL" ]]; then
if [[ ! -f $PACMAN_WRAPPER_INSTALL ]]; then
missing_files+=("$PACMAN_WRAPPER_INSTALL")
fi
if [[ ! -f "$HOSTS_INSTALL_SCRIPT" ]]; then
if [[ ! -f $HOSTS_INSTALL_SCRIPT ]]; then
missing_files+=("$HOSTS_INSTALL_SCRIPT")
fi
@ -114,7 +114,7 @@ verify_files() {
"$TEMPLATE_STARTUP" \
"$TEMPLATE_HOSTS_SVC" \
"$TEMPLATE_LOGROTATE"; do
if [[ ! -f "$tmpl" ]]; then
if [[ ! -f $tmpl ]]; then
missing_files+=("$tmpl")
fi
done
@ -215,11 +215,11 @@ install_browser_preexec_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
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"
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"
@ -227,7 +227,7 @@ install_browser_preexec_wrapper() {
# 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" )
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"
@ -292,7 +292,7 @@ run_initial_execution() {
local run_initial=true
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
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
@ -304,7 +304,7 @@ run_initial_execution() {
echo "Auto-running initial execution to test the setup (use --interactive to prompt)"
fi
if [[ "$run_initial" == "true" ]]; then
if [[ $run_initial == "true" ]]; then
echo "Running initial system maintenance..."
/usr/local/bin/periodic-system-maintenance.sh
echo "✓ Initial execution completed"

View File

@ -10,11 +10,11 @@ INTERACTIVE_MODE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i|--interactive)
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h|--help)
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
@ -46,7 +46,7 @@ echo "=================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
@ -83,7 +83,7 @@ check_thorium_browser() {
local found=false
for path in "${alt_paths[@]}"; do
if [[ -x "$path" ]]; then
if [[ -x $path ]]; then
BROWSER_COMMAND="$path"
echo "✓ Found Thorium browser at: $path"
found=true
@ -91,7 +91,7 @@ check_thorium_browser() {
fi
done
if [[ "$found" != true ]]; then
if [[ $found != true ]]; then
echo "Error: Thorium browser not found!"
echo "Please install Thorium browser first or ensure it's in your PATH."
echo ""
@ -101,7 +101,7 @@ check_thorium_browser() {
local continue_anyway=false
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
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
@ -112,7 +112,7 @@ check_thorium_browser() {
continue_anyway=true
fi
if [[ "$continue_anyway" != true ]]; then
if [[ $continue_anyway != true ]]; then
exit 1
fi
fi
@ -330,7 +330,7 @@ create_i3_autostart() {
sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir"
# Check if i3 config exists
if [[ -f "$i3_config" ]]; then
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
@ -369,7 +369,7 @@ EOF
# Add to user's .bashrc to run on next login
local bashrc="$USER_HOME/.bashrc"
if [[ -f "$bashrc" ]]; then
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'"
@ -407,7 +407,7 @@ test_setup() {
local run_test=true
if [[ "$INTERACTIVE_MODE" == "true" ]]; then
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
@ -419,7 +419,7 @@ test_setup() {
echo "Auto-testing the browser launcher (use --interactive to prompt)"
fi
if [[ "$run_test" == "true" ]]; then
if [[ $run_test == "true" ]]; then
echo "Testing browser launch..."
echo "Note: This will open Thorium browser with Fitatu website"

View File

@ -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" "$@"

View File

@ -17,24 +17,24 @@ log_message() {
# Function to check if hosts file needs restoration
needs_restoration() {
# Check if file exists
if [[ ! -f "$HOSTS_FILE" ]]; then
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
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
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
if ! grep -q "StevenBlack" "$HOSTS_FILE" 2> /dev/null; then
return 0 # StevenBlack entries missing, needs restoration
fi
@ -45,7 +45,7 @@ needs_restoration() {
restore_hosts_file() {
log_message "Hosts file modification detected - initiating restoration"
if [[ -f "$HOSTS_INSTALL_SCRIPT" ]]; then
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
@ -63,10 +63,10 @@ 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 | \
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
if [[ $file == "$HOSTS_FILE" ]] || [[ $file == "/etc/hosts" ]]; then
log_message "Event detected: $event on $file at $time"
# Small delay to avoid rapid-fire events
@ -100,7 +100,7 @@ monitor_with_polling() {
log_message "=== Hosts File Monitor Started ==="
# Check if inotify-tools is available
if command -v inotifywait >/dev/null 2>&1; then
if command -v inotifywait > /dev/null 2>&1; then
log_message "Using inotify for file monitoring"
monitor_with_inotify
else

View File

@ -24,7 +24,7 @@ execute_with_log() {
log_message "Starting $script_name"
echo "Executing $script_name..." >&2
if [[ -f "$script_path" ]]; then
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

View File

@ -5,7 +5,6 @@
set -euo pipefail
DOWNLOADS_DIR="$HOME/Downloads"
HOME_DIR="$HOME"
# Test function
test_file_removal() {
@ -14,7 +13,7 @@ test_file_removal() {
# 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)
done < <(find "$DOWNLOADS_DIR" -name "*.jpg" -print0 2> /dev/null | head -z -n 2)
echo "Found ${#files[@]} test files:"
for file in "${files[@]}"; do
@ -27,7 +26,7 @@ test_file_removal() {
for file in "${files[@]}"; do
echo "Removing: $file"
if rm "$file" 2>/dev/null; then
if rm "$file" 2> /dev/null; then
echo " SUCCESS"
((removed++))
else

View File

@ -16,7 +16,7 @@ fi
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

View File

@ -13,7 +13,7 @@ SAMPLE_LIMIT=20
# Simple usage helper
usage() {
cat <<EOF
cat << EOF
Usage: $(basename "$0") [--dry-run|-n] [--sample=N]
Options:
@ -42,7 +42,7 @@ log() {
# Parse CLI flags early
while [[ ${1:-} =~ ^- ]]; do
case "${1}" in
-n|--dry-run)
-n | --dry-run)
DRY_RUN=true
shift
;;
@ -50,7 +50,7 @@ while [[ ${1:-} =~ ^- ]]; do
SAMPLE_LIMIT="${1#*=}"
shift
;;
-h|--help)
-h | --help)
usage
exit 0
;;
@ -70,14 +70,14 @@ is_media_file() {
# Check if it's an image
for ext in "${IMAGE_EXTENSIONS[@]}"; do
if [[ "$extension" == "$ext" ]]; then
if [[ $extension == "$ext" ]]; then
return 0
fi
done
# Check if it's a video
for ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ "$extension" == "$ext" ]]; then
if [[ $extension == "$ext" ]]; then
return 0
fi
done
@ -96,24 +96,25 @@ find_media_files() {
".media_organize_" "media_organize_"
)
if [[ "$search_dir" == "$HOME_DIR" ]]; then
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")
local basename
basename=$(basename "$file")
# Skip hidden files and common system directories
if [[ ! "$basename" =~ ^\. ]] && [[ -f "$file" ]]; then
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)
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 )
prune_expr+=(-name "$ex*" -o)
done
# Remove trailing -o
unset 'prune_expr[${#prune_expr[@]}-1]'
@ -122,7 +123,7 @@ find_media_files() {
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)
done < <(find "$search_dir" \( -type d \( "${prune_expr[@]}" \) -prune \) -o -type f -print0 2> /dev/null)
fi
printf '%s\n' "${files[@]}"
@ -138,12 +139,13 @@ create_media_archive() {
fi
# Create timestamp for archive name
local timestamp=$(date '+%Y%m%d_%H%M%S')
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
if ! mkdir -p "$TEMP_DIR" 2> /dev/null; then
TEMP_DIR="/tmp/media_organize_$$"
mkdir -p "$TEMP_DIR"
fi
@ -159,20 +161,21 @@ create_media_archive() {
local copy_errors=0
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
if [[ -f $file ]]; then
local relative_path=""
if [[ "$file" == "$DOWNLOADS_DIR"* ]]; then
relative_path="downloads/${file#$DOWNLOADS_DIR/}"
if [[ $file == "$DOWNLOADS_DIR"* ]]; then
relative_path="downloads/${file#"$DOWNLOADS_DIR"/}"
else
relative_path="home/${file#$HOME_DIR/}"
relative_path="home/${file#"$HOME_DIR"/}"
fi
local temp_file="$TEMP_DIR/$relative_path"
local temp_dir=$(dirname "$temp_file")
local temp_dir
temp_dir=$(dirname "$temp_file")
mkdir -p "$temp_dir"
# Check readability first to provide a clearer error
if [[ ! -r "$file" ]]; then
if [[ ! -r $file ]]; then
log "WARNING: Cannot read $file (permission denied?)"
((copy_errors++))
continue
@ -211,14 +214,15 @@ create_media_archive() {
log "Successfully created archive with ${#successfully_copied[@]} files."
# Verify the zip file was actually created and is not empty
if [[ ! -f "$archive_path" ]]; then
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
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
@ -234,8 +238,8 @@ create_media_archive() {
set +e
for file in "${successfully_copied[@]}"; do
if [[ -f "$file" ]]; then
if rm "$file" 2>/dev/null; then
if [[ -f $file ]]; then
if rm "$file" 2> /dev/null; then
removed_count=$((removed_count + 1))
log "Removed: $(basename "$file")"
else
@ -279,18 +283,18 @@ main() {
log "Starting media file organization..."
# Check if required directories exist
if [[ ! -d "$DOWNLOADS_DIR" ]]; then
if [[ ! -d $DOWNLOADS_DIR ]]; then
log "ERROR: Downloads directory not found: $DOWNLOADS_DIR"
exit 1
fi
if [[ ! -d "$HOME_DIR" ]]; then
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
if ! command -v zip > /dev/null 2>&1; then
log "ERROR: zip command not found. Please install zip package."
exit 1
fi
@ -302,13 +306,13 @@ main() {
# Find files in Downloads directory
log "Scanning Downloads directory..."
while IFS= read -r file; do
[[ -n "$file" ]] && all_files+=("$file")
[[ -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")
[[ -n $file ]] && all_files+=("$file")
done < <(find_media_files "$HOME_DIR")
if $DRY_RUN; then
@ -334,10 +338,10 @@ main() {
((ext_counts["$ext"]++)) || true
# Top directory under Downloads
if [[ "$f" == "$DOWNLOADS_DIR"/* ]]; then
if [[ $f == "$DOWNLOADS_DIR"/* ]]; then
rel="${f#"$DOWNLOADS_DIR"/}"
topdir="${rel%%/*}"
[[ "$topdir" == "$rel" ]] && topdir="."
[[ $topdir == "$rel" ]] && topdir="."
((dir_counts["$topdir"]++)) || true
else
((dir_counts["~"]++)) || true

View File

@ -17,7 +17,7 @@ log() {
}
# Check if organize script exists
if [[ ! -f "$ORGANIZE_SCRIPT" ]]; then
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
exit 1
fi

View File

@ -46,7 +46,7 @@ echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
# Verify we have a valid user
if [[ -z "${SUDO_USER}" ]]; then
if [[ -z ${SUDO_USER} ]]; then
echo "Error: Could not determine the original user. Please run this script with sudo."
exit 1
fi
@ -57,8 +57,10 @@ 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)"
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
@ -165,7 +167,7 @@ EOF
echo "✓ LightDM service enabled"
# Restart lightdm to apply changes only if --logout flag is provided
if [[ "$ALLOW_LOGOUT" == true ]]; then
if [[ $ALLOW_LOGOUT == true ]]; then
echo "Restarting LightDM to apply auto-login settings..."
systemctl restart lightdm.service
echo "✓ LightDM restarted"
@ -187,7 +189,7 @@ configure_i3_session() {
mkdir -p "$xsessions_dir"
# Check if i3.desktop exists, create if not
if [[ ! -f "$i3_desktop" ]]; then
if [[ ! -f $i3_desktop ]]; then
cat > "$i3_desktop" << EOF
[Desktop Entry]
Name=i3
@ -208,7 +210,7 @@ EOF
local user_home="/home/${TARGET_USER}"
local i3_config_dir="$user_home/.config/i3"
if [[ ! -d "$i3_config_dir" ]]; then
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
@ -233,7 +235,7 @@ configure_additional_settings() {
# Configure pam for auto-login (if needed)
local pam_lightdm="/etc/pam.d/lightdm-autologin"
if [[ ! -f "$pam_lightdm" ]]; then
if [[ ! -f $pam_lightdm ]]; then
cat > "$pam_lightdm" << EOF
#%PAM-1.0
# LightDM auto-login PAM configuration
@ -259,7 +261,7 @@ test_configurations() {
# Test sudo configuration
echo "Testing passwordless sudo..."
if sudo -u "$TARGET_USER" sudo -n true 2>/dev/null; then
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)"
@ -267,7 +269,7 @@ test_configurations() {
# Test lightdm configuration
echo "Testing LightDM configuration..."
if lightdm --test-mode --debug 2>/dev/null | grep -q "seat"; then
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)"
@ -351,7 +353,7 @@ show_security_warnings
show_final_instructions
# Only offer reboot if --reboot flag was provided
if [[ "$OFFER_REBOOT" == true ]]; then
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

View File

@ -3,19 +3,29 @@
# 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

View File

@ -29,7 +29,7 @@ trap on_abort INT TERM
# --------------------------- CLI args ---------------------------
usage() {
cat <<USAGE
cat << USAGE
Usage: $SCRIPT_NAME [--refresh] [--clear-cache] [--verbose] [--help]
Options:
@ -48,26 +48,38 @@ parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--refresh)
FORCE_REFRESH=1; shift ;;
FORCE_REFRESH=1
shift
;;
--clear-cache)
CLEAR_CACHE=1; FORCE_REFRESH=1; shift ;;
-v|--verbose)
VERBOSE=1; shift ;;
-h|--help)
usage; exit 0 ;;
CLEAR_CACHE=1
FORCE_REFRESH=1
shift
;;
-v | --verbose)
VERBOSE=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
die "Unknown option: $1 (use --help)" ;;
die "Unknown option: $1 (use --help)"
;;
esac
done
}
log() { printf "[%s] %s\n" "$SCRIPT_NAME" "$*" >&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
@ -75,7 +87,7 @@ for cmd in curl jq awk sed grep sort lspci free uname; do
done
HAS_TIMEOUT=0
if command -v timeout >/dev/null 2>&1; then
if command -v timeout > /dev/null 2>&1; then
HAS_TIMEOUT=1
fi
@ -86,10 +98,10 @@ http_get() {
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
-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
-H "User-Agent: $ua" -H 'Accept: application/json, text/plain, */*' "$url" "$@" 2> /dev/null
fi
}
@ -100,46 +112,52 @@ 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
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
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";
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";
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) ))
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 ))
SYSTEM_RAM_GB=$(((mem_mb + 1023) / 1024))
fi
}
@ -154,38 +172,65 @@ cpu_class_rank() {
}
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
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
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/&nbsp;/ /g; s/&amp;/\&/g; s/\r//g' <<<"$1"
sed -E 's/<[^>]+>//g; s/&nbsp;/ /g; s/&amp;/\&/g; s/\r//g' <<< "$1"
}
parse_ram_gb() {
# Extract first RAM mention and convert to GB integer
local text="$1"
local num unit val
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
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
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
}
@ -201,14 +246,14 @@ RESULTS_CACHE="$CACHE_DIR/results.tsv"
load_credentials() {
# Prefer environment, else config file
if [[ -n "${STEAM_API_KEY:-}" && -n "${STEAM_ID64:-}" ]]; then
if [[ -n ${STEAM_API_KEY:-} && -n ${STEAM_ID64:-} ]]; then
return 0
fi
if [[ -r "$CONFIG_FILE" ]]; then
if [[ -r $CONFIG_FILE ]]; then
# shellcheck disable=SC1090
. "$CONFIG_FILE" || true
fi
if [[ -z "${STEAM_API_KEY:-}" || -z "${STEAM_ID64:-}" ]]; then
if [[ -z ${STEAM_API_KEY:-} || -z ${STEAM_ID64:-} ]]; then
return 1
fi
return 0
@ -217,9 +262,9 @@ load_credentials() {
save_credentials() {
local key="$1" id="$2"
mkdir -p "$CONFIG_DIR"
chmod 700 "$CONFIG_DIR" 2>/dev/null || true
chmod 700 "$CONFIG_DIR" 2> /dev/null || true
umask 177
cat > "$CONFIG_FILE" <<EOF
cat > "$CONFIG_FILE" << EOF
# Saved by $SCRIPT_NAME
STEAM_API_KEY="$key"
STEAM_ID64="$id"
@ -242,11 +287,11 @@ prompt_for_credentials() {
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
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
if ! grep -qE '^765[0-9]{14}$' <<< "$id"; then
log "Warning: Steam ID64 format unexpected; continuing anyway."
fi
STEAM_API_KEY="$key"
@ -256,37 +301,37 @@ prompt_for_credentials() {
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
[[ -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
if [[ -n $paths ]]; then
while IFS= read -r p; do
[[ -d "$p/steamapps" ]] && dirs+=("$p/steamapps")
done <<<"$paths"
done <<< "$paths"
fi
fi
[[ -d "$base/steamapps" ]] && dirs+=("$base/steamapps")
done
# de-dupe
printf "%s\n" "${dirs[@]}" 2>/dev/null | awk '!seen[$0]++'
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
[[ -d $d ]] || continue
for mf in "$d"/appmanifest_*.acf; do
[[ -f "$mf" ]] || continue
[[ -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
if [[ -n $appid ]]; then
printf "%s\t%s\n" "$appid" "${name:-Unknown}"
fi
done
@ -295,7 +340,7 @@ list_installed_games() {
list_owned_games_via_api() {
local key="${STEAM_API_KEY:-}" sid="${STEAM_ID64:-}"
if [[ -z "$key" || -z "$sid" ]]; then
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"
@ -327,8 +372,8 @@ 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
end' 2> /dev/null <<< "$json") || true
if [[ -z $out ]]; then
out=$'fail false false false '
fi
printf '%s\n' "$out"
@ -349,8 +394,8 @@ 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
end' 2> /dev/null) || true
if [[ -z $out ]]; then
out=$'fail\tfalse\tfalse\tfalse\t\t\t'
fi
printf '%s\n' "$out"
@ -361,7 +406,7 @@ score_game() {
local score=0
# Linux platform support
if [[ "$linux_support" == "true" ]]; then
if [[ $linux_support == "true" ]]; then
score=$((score + 50))
else
score=$((score + 20)) # Assume Proton potential
@ -376,34 +421,34 @@ score_game() {
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
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
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
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
if [[ $req_gpu == "unknown" ]]; then
score=$((score + 5))
elif [[ "$req_gpu" == "$vendor" ]]; then
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
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))
@ -420,34 +465,34 @@ print_header() {
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
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
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
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
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"
@ -461,19 +506,20 @@ fetch_protondb_tier() {
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
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'
tr '[:upper:]' '[:lower:]' <<< "$tier" | tr -d '\r\n'
fi
}
protondb_allowed() {
local tier="$(tr '[:upper:]' '[:lower:]' <<<"${1:-}")"
local tier
tier=$(tr '[:upper:]' '[:lower:]' <<< "${1:-}")
case "$tier" in
platinum|native|gold|silver|unknown|"") return 0 ;;
bronze|pending|borked|unsupported|broken) return 1 ;;
platinum | native | gold | silver | unknown | "") return 0 ;;
bronze | pending | borked | unsupported | broken) return 1 ;;
*) return 0 ;;
esac
}
@ -498,11 +544,11 @@ main() {
# 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
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
if [[ ! -s $games_tsv ]]; then
die "No games found from Steam Web API. Check STEAM_API_KEY/STEAM_ID64 and network connectivity."
fi
@ -510,7 +556,7 @@ main() {
check_network_or_exit
ensure_cache_dir
if [[ $CLEAR_CACHE -eq 1 ]] && [[ -f "$RESULTS_CACHE" ]]; then
if [[ $CLEAR_CACHE -eq 1 ]] && [[ -f $RESULTS_CACHE ]]; then
rm -f "$RESULTS_CACHE" || true
log "Cleared cache: $RESULTS_CACHE"
fi
@ -526,20 +572,20 @@ main() {
local count=0
local total
total=$(wc -l < "$games_tsv" | tr -d ' ')
[[ -z "$total" ]] && total=0
[[ -z $total ]] && total=0
while IFS=$'\t' read -r appid name; do
[[ $ABORT -eq 1 ]] && break
[[ -n "$appid" ]] || continue
[[ -n $appid ]] || continue
if is_known_tool_name "$name"; then
vlog "[$((count+1))/$total] Skipping compatibility tool: $name ($appid)"
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
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]}"
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"
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
@ -551,29 +597,29 @@ main() {
# 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"
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
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"
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
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"
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
if [[ $status != "ok" ]]; then continue; fi
if [[ $type != "game" && $type != "dlc" && $type != "" ]]; then continue; fi
# ProtonDB tier
[[ $ABORT -eq 1 ]] && break
local pdb_tier
@ -584,7 +630,7 @@ main() {
# 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"
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
@ -595,7 +641,7 @@ main() {
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
if [[ ! -s $results_combined ]]; then
die "No compatible entries parsed from store API."
fi
@ -611,8 +657,7 @@ main() {
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"
cp -f "$results_combined" "$RESULTS_CACHE" 2> /dev/null || cat "$results_combined" > "$RESULTS_CACHE"
}
main "$@"

View File

@ -13,7 +13,7 @@ if ! pgrep -x "dbus-daemon" > /dev/null; then
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

View File

@ -15,7 +15,7 @@ if [[ $EUID -ne 0 ]]; then
fi
# Check if action parameter is provided
if [[ "$ACTION" != "on" && "$ACTION" != "off" ]]; then
if [[ $ACTION != "on" && $ACTION != "off" ]]; then
echo "Usage: $0 [on|off]"
exit 1
fi
@ -27,7 +27,7 @@ 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
if [[ $v == "$VENDOR_ID" && $p == "$PRODUCT_ID" ]]; then
DEVICE_PATH="$d"
break
fi

View File

@ -15,12 +15,15 @@ TARGET_PACKAGES=(
# 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
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."
@ -29,7 +32,7 @@ require_command() {
ensure_pacman() {
require_command pacman "pacman"
if ! grep -qi "arch" /etc/os-release 2>/dev/null; then
if ! grep -qi "arch" /etc/os-release 2> /dev/null; then
warn "This script was designed for Arch Linux; continuing anyway."
fi
}
@ -37,7 +40,7 @@ ensure_pacman() {
install_packages() {
local missing=()
for pkg in "${TARGET_PACKAGES[@]}"; do
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
if ! pacman -Qi "$pkg" > /dev/null 2>&1; then
missing+=("$pkg")
fi
done
@ -47,7 +50,7 @@ install_packages() {
return
fi
if ! command -v sudo >/dev/null 2>&1; then
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
@ -56,7 +59,7 @@ install_packages() {
}
print_post_install_tips() {
cat <<EOF
cat << EOF
------------------------------------------------------------------------
XFCE session installed.
@ -75,13 +78,13 @@ EOF
logout_user() {
local session_id="${XDG_SESSION_ID:-}"
if [[ -n "$session_id" ]] && loginctl show-session "$session_id" >/dev/null 2>&1; then
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
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

View File

@ -22,7 +22,7 @@ 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
@ -34,8 +34,8 @@ for arg in "${@:-}"; do
--watch-controller)
watch_controller=true
;;
-h|--help)
cat <<EOF
-h | --help)
cat << EOF
Usage: $(basename "$0") [--persist-systemd] [--watch-controller]
Disables idle detection, screen blanking, and auto-lock for the current session.
@ -60,7 +60,7 @@ EOF
done
disable_x11_idle() {
if [[ -n "${DISPLAY:-}" ]] && has_cmd xset; then
if [[ -n ${DISPLAY:-} ]] && has_cmd xset; then
log "Disabling X11 DPMS/screensaver/blanking via xset"
xset -dpms || true
xset s off || true
@ -73,17 +73,17 @@ disable_x11_idle() {
disable_gnome_idle() {
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
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"
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"
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
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
gsettings set org.gnome.desktop.screensaver idle-activation-enabled false 2> /dev/null || true
fi
fi
}
@ -92,16 +92,16 @@ 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
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
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
@ -113,13 +113,13 @@ disable_lock_daemons() {
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
if pgrep -x "$d" > /dev/null 2>&1; then
found=true
log "Stopping ${d}"
pkill -x "$d" || true
fi
done
if [[ "$found" == false ]]; then
if [[ $found == false ]]; then
log "No known lock daemons running"
fi
}
@ -130,15 +130,15 @@ disable_tty_idle() {
# 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
[[ -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 [[ -n ${DISPLAY:-} ]]; then
if has_cmd xset; then
xset s reset || true
xset -dpms || true
@ -147,7 +147,7 @@ reset_idle_activity() {
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
xdotool mousemove_relative -- 0 0 2> /dev/null || true
fi
fi
}
@ -156,7 +156,7 @@ watch_js_device() {
local dev="$1"
log "Watching controller device: $dev"
while :; do
if [[ ! -e "$dev" ]]; then
if [[ ! -e $dev ]]; then
warn "Device disappeared: $dev"
break
fi
@ -174,32 +174,31 @@ watch_js_device() {
start_controller_watchers() {
# Attempt to watch all /dev/input/js* devices; rescan periodically for new ones
local seen=""
declare -A pids
# Initial permission check
local any_js=false any_readable=false
for dev in /dev/input/js*; do
[[ -e "$dev" ]] || continue
[[ -e $dev ]] || continue
any_js=true
if [[ -r "$dev" ]]; then any_readable=true; fi
if [[ -r $dev ]]; then any_readable=true; fi
done
if [[ "$any_js" == true && "$any_readable" == false ]]; then
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
[[ -e $dev ]] || continue
found_any=true
if [[ -z "${pids[$dev]:-}" ]] || ! kill -0 "${pids[$dev]}" 2>/dev/null; then
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
if [[ $found_any == false ]]; then
# No joystick devices; quiet rescan
sleep 5
else
@ -212,7 +211,7 @@ start_controller_watchers() {
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
if [[ $persist_systemd != true ]]; then
return 0
fi
if ! has_cmd sudo; then
@ -255,7 +254,7 @@ main() {
# Optional persistence
persist_with_systemd_logind
if [[ "$watch_controller" == true ]]; then
if [[ $watch_controller == true ]]; then
log "Controller activity watcher enabled"
# Keep the script alive to watch controllers
start_controller_watchers &
@ -270,4 +269,3 @@ main() {
}
main "$@"