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,17 +2,20 @@
# Lightweight GPU detection script. # Lightweight GPU detection script.
# Detects GPU vendor and invokes the corresponding vendor install/management script. # Detects GPU vendor and invokes the corresponding vendor install/management script.
# Exports: GPU_VENDOR # Exports: GPU_VENDOR
# shellcheck source=./install_nvidia_driver.sh
# shellcheck source=./install_amd_driver.sh
# shellcheck source=./install_intel_driver.sh
set -e set -e
GPU_VENDOR="unknown" GPU_VENDOR="unknown"
PCI_GPU_INFO=$(lspci -nn | grep -Ei 'vga|3d|display' || true) PCI_GPU_INFO=$(lspci -nn | grep -Ei 'vga|3d|display' || true)
if echo "$PCI_GPU_INFO" | grep -qi nvidia; then if echo "$PCI_GPU_INFO" | grep -qi nvidia; then
GPU_VENDOR="nvidia" GPU_VENDOR="nvidia"
elif echo "$PCI_GPU_INFO" | grep -Eqi '\b(amd|advanced micro devices|ati)\b'; then elif echo "$PCI_GPU_INFO" | grep -Eqi '\b(amd|advanced micro devices|ati)\b'; then
GPU_VENDOR="amd" GPU_VENDOR="amd"
elif echo "$PCI_GPU_INFO" | grep -qi intel; then elif echo "$PCI_GPU_INFO" | grep -qi intel; then
GPU_VENDOR="intel" GPU_VENDOR="intel"
fi fi
export GPU_VENDOR export GPU_VENDOR
@ -21,6 +24,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
case "$GPU_VENDOR" in case "$GPU_VENDOR" in
nvidia) nvidia)
if [ -x "$SCRIPT_DIR/install_nvidia_driver.sh" ]; then if [ -x "$SCRIPT_DIR/install_nvidia_driver.sh" ]; then
# shellcheck source=./install_nvidia_driver.sh disable=SC1091
. "$SCRIPT_DIR/install_nvidia_driver.sh" . "$SCRIPT_DIR/install_nvidia_driver.sh"
else else
echo "NVIDIA installer script missing: $SCRIPT_DIR/install_nvidia_driver.sh" echo "NVIDIA installer script missing: $SCRIPT_DIR/install_nvidia_driver.sh"
@ -28,6 +32,7 @@ case "$GPU_VENDOR" in
;; ;;
amd) amd)
if [ -x "$SCRIPT_DIR/install_amd_driver.sh" ]; then if [ -x "$SCRIPT_DIR/install_amd_driver.sh" ]; then
# shellcheck source=./install_amd_driver.sh disable=SC1091
. "$SCRIPT_DIR/install_amd_driver.sh" . "$SCRIPT_DIR/install_amd_driver.sh"
else else
echo "AMD installer script missing: $SCRIPT_DIR/install_amd_driver.sh (placeholder)" echo "AMD installer script missing: $SCRIPT_DIR/install_amd_driver.sh (placeholder)"
@ -35,6 +40,7 @@ case "$GPU_VENDOR" in
;; ;;
intel) intel)
if [ -x "$SCRIPT_DIR/install_intel_driver.sh" ]; then if [ -x "$SCRIPT_DIR/install_intel_driver.sh" ]; then
# shellcheck source=./install_intel_driver.sh disable=SC1091
. "$SCRIPT_DIR/install_intel_driver.sh" . "$SCRIPT_DIR/install_intel_driver.sh"
else else
echo "Intel installer script missing: $SCRIPT_DIR/install_intel_driver.sh" echo "Intel installer script missing: $SCRIPT_DIR/install_intel_driver.sh"

View File

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Backwards compatibility wrapper; prefer using detect_gpu.sh directly. # Backwards compatibility wrapper; prefer using detect_gpu.sh directly.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./detect_gpu.sh disable=SC1091
. "$SCRIPT_DIR/detect_gpu.sh" . "$SCRIPT_DIR/detect_gpu.sh"

View File

@ -12,7 +12,10 @@
# AMD_VERBOSE=1 # verbose output # AMD_VERBOSE=1 # verbose output
set -e 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_XF86=${AMD_INSTALL_XF86:-0}
AMD_INSTALL_AMDVLK=${AMD_INSTALL_AMDVLK:-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) # Simple AUR builder (reused from NVIDIA script style)
_build_aur_pkg() { _build_aur_pkg() {
local pkg="$1" url="https://aur.archlinux.org/${pkg}.git" local pkg="$1"
mkdir -p "$HOME/aur"; cd "$HOME/aur" 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 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 yes | makepkg -s -c -C --noconfirm --needed
local built=( *.pkg.tar.zst ) local built=(*.pkg.tar.zst)
yes | sudo pacman -U --noconfirm "${built[@]}" yes | sudo pacman -U --noconfirm "${built[@]}"
} }
_install_repo_or_aur() { _install_repo_or_aur() {
local pkg="$1" local pkg="$1"
if pacman -Si "$pkg" >/dev/null 2>&1; then if pacman -Si "$pkg" > /dev/null 2>&1; then
if pacman -Qi "$pkg" >/dev/null 2>&1; then if pacman -Qi "$pkg" > /dev/null 2>&1; then
vlog "$pkg already installed" vlog "$pkg already installed"
else else
yes | sudo pacman -Sy --noconfirm "$pkg" 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) GPU_LINES=$(lspci -nn | grep -Ei 'vga|3d|display' | grep -iE 'amd|ati' || true)
SI_NAMES=(Tahiti Pitcairn Cape Verde Oland Hainan Curacao) SI_NAMES=(Tahiti Pitcairn Cape Verde Oland Hainan Curacao)
CIK_NAMES=(Bonaire Hawaii Kabini Kaveri Mullins Temash Spectre Spooky) 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 "${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 for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done
@ -135,7 +142,7 @@ else
fi fi
# Check active kernel driver # 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}') [ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}" info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -12,7 +12,10 @@
# INTEL_VERBOSE=0/1 # verbose logging # INTEL_VERBOSE=0/1 # verbose logging
set -e 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_USE_AMBER=${INTEL_USE_AMBER:-0}
INTEL_INSTALL_LIB32=${INTEL_INSTALL_LIB32:-auto} INTEL_INSTALL_LIB32=${INTEL_INSTALL_LIB32:-auto}
@ -41,10 +44,10 @@ fi
install_pkg() { install_pkg() {
local pkg="$1" 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" vlog "$pkg already installed"
else 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" yes | sudo pacman -Sy --noconfirm "$pkg"
else else
warn "Package $pkg not found in repos (not handling AUR here)" warn "Package $pkg not found in repos (not handling AUR here)"
@ -86,7 +89,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
else else
info "Configuring enable_guc=$INTEL_ENABLE_GUC" info "Configuring enable_guc=$INTEL_ENABLE_GUC"
sudo mkdir -p /etc/modprobe.d 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 if [ "$INTEL_SKIP_INITRAMFS" != 1 ] && [ -f /etc/mkinitcpio.conf ]; then
info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change" info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change"
sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually" sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually"
@ -97,7 +100,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
fi fi
# Report kernel driver # 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}') [ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'i915|xe' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}" info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -4,15 +4,22 @@
# Outputs: NVIDIA_DRIVER_PACKAGE # Outputs: NVIDIA_DRIVER_PACKAGE
set -e 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() { _build_aur_pkg() {
local pkg="$1"; local repo_url="https://aur.archlinux.org/${pkg}.git"; local pkg="$1"
mkdir -p "$HOME/aur"; cd "$HOME/aur"; 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 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 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() { _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 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) # 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 [ -z "${NVIDIA_SKIP_DETECT:-}" ] && ! command -v nvidia-detect > /dev/null 2>&1; then
if pacman -Si 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 echo "Attempting to install helper utility: nvidia-detect" >&2
# Use --needed to avoid forcing refresh (& avoid partial upgrade semantics with -Sy) # 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 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
fi fi
if command -v nvidia-detect >/dev/null 2>&1; then if command -v nvidia-detect > /dev/null 2>&1; then
detect_out="$(nvidia-detect 2>/dev/null || true)" detect_out="$(nvidia-detect 2> /dev/null || true)"
fi fi
if [ -n "$detect_out" ]; then 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 '470'; then
if echo "$detect_out" | grep -q '390'; then driver_pkg='nvidia-390xx-dkms'; legacy_detected=1; fi driver_pkg='nvidia-470xx-dkms'
if echo "$detect_out" | grep -q '340'; then driver_pkg='nvidia-340xx-dkms'; legacy_detected=1; fi 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 fi
if [ "$legacy_detected" = 0 ]; then if [ "$legacy_detected" = 0 ]; then
# Heuristic modern driver selection # Heuristic modern driver selection
if [ "$multiple_kernels" = 1 ]; then 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 else
if [ "$have_linux_lts" = 1 ] && [ "$have_linux" = 0 ]; then 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 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
fi fi
else else
@ -62,21 +78,22 @@ _choose_nvidia_pkg() {
} }
_remove_conflicting_nvidia_pkgs() { _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=() local to_remove=()
for p in "${candidates[@]}"; do 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 done
if [ ${#to_remove[@]} -gt 0 ]; then yes | sudo pacman -Rns --noconfirm "${to_remove[@]}" || true; fi if [ ${#to_remove[@]} -gt 0 ]; then yes | sudo pacman -Rns --noconfirm "${to_remove[@]}" || true; fi
} }
_install_nvidia_stack() { _install_nvidia_stack() {
local driver_pkg="$1" 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" 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 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 fi
} }

View File

@ -1,119 +1,124 @@
#!/bin/sh #!/usr/bin/env bash
# shellcheck source=./detect_gpu.sh
# shellcheck source=./detect_gpu_and_install.sh
set -e set -e
# Function to play a sound on error # Function to play a sound on error
play_error_sound() { play_error_sound() {
#pactl set-sink-volume @DEFAULT_SINK@ +50% #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 paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga
done done
#pactl set-sink-volume @DEFAULT_SINK@ -50% #pactl set-sink-volume @DEFAULT_SINK@ -50%
} }
# Trap errors and call the play_error_sound function # Trap errors and call the play_error_sound function
trap 'play_error_sound' ERR trap 'play_error_sound' ERR
sudo -v sudo -v
git config --global init.defaultBranch main git config --global init.defaultBranch main
# GPU detection (now split vendor-specific logic) # GPU detection (now split vendor-specific logic)
if [ -f "./detect_gpu.sh" ]; then if [ -f "./detect_gpu.sh" ]; then
. ./detect_gpu.sh # shellcheck source=./detect_gpu.sh disable=SC1091
. ./detect_gpu.sh
elif [ -f "./detect_gpu_and_install.sh" ]; then elif [ -f "./detect_gpu_and_install.sh" ]; then
. ./detect_gpu_and_install.sh # shellcheck source=./detect_gpu_and_install.sh disable=SC1091
. ./detect_gpu_and_install.sh
else else
echo "GPU detection scripts not found; continuing without GPU specific installation." echo "GPU detection scripts not found; continuing without GPU specific installation."
fi fi
install_from_aur() { install_from_aur() {
if [ ! -d "$HOME/aur" ]; then local repo_url pkg_name repo_dir
mkdir -p "$HOME/aur" repo_url="$1"
fi pkg_name="$2"
cd "$HOME/aur"
local repo_url=$1
local pkg_name=$2
local repo_dir="$(basename "$repo_url" .git)"
if [ ! -d "$repo_dir" ]; then mkdir -p "$HOME/aur"
git clone "$repo_url" cd "$HOME/aur" || return 1
else repo_dir="$(basename "$repo_url" .git)"
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"
if pacman -Qi "$pkg_name" >/dev/null 2>&1; then if [ ! -d "$repo_dir" ]; then
echo "$pkg_name is already installed" git clone "$repo_url"
return 0 else
fi echo "Repository $repo_dir already cloned; updating"
(cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true)
fi
cd "$repo_dir" || return 1
echo "Cleaning old package artifacts to avoid duplicate -U targets" if pacman -Qi "$pkg_name" > /dev/null 2>&1; then
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true echo "$pkg_name is already installed"
return 0
fi
echo "Building $pkg_name (clean build)" echo "Cleaning old package artifacts to avoid duplicate -U targets"
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true
if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then
echo "Build failed for $pkg_name" >&2
return 1
fi
# Collect only the freshly built packages (should now be only current version) echo "Building $pkg_name (clean build)"
mapfile -t built_pkgs < <(ls -1 *.pkg.tar.zst 2>/dev/null || true) # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
if [ ${#built_pkgs[@]} -eq 0 ]; then if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then
echo "No package files produced for $pkg_name" >&2 echo "Build failed for $pkg_name" >&2
return 1 return 1
fi fi
echo "Installing built package(s): ${built_pkgs[*]}" # Collect only the freshly built packages (should now be only current version)
if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n')
echo "Installation failed for $pkg_name" >&2 if [ ${#built_pkgs[@]} -eq 0 ]; then
return 1 echo "No package files produced for $pkg_name" >&2
fi return 1
fi
echo "Installing built package(s): ${built_pkgs[*]}"
if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then
echo "Installation failed for $pkg_name" >&2
return 1
fi
} }
process_packages() { process_packages() {
local file_path=$1 local file_path
> failed.txt file_path="$1"
> done.txt : > failed.txt
: > done.txt
while IFS= read -r pkg_name; do while IFS= read -r pkg_name; do
if [ -z "$pkg_name" ]; then if [ -z "$pkg_name" ]; then
continue continue
fi fi
local repo_url="https://aur.archlinux.org/${pkg_name}-git.git" local repo_url repo_dir
local repo_dir="${pkg_name}-git" repo_url="https://aur.archlinux.org/${pkg_name}-git.git"
repo_dir="${pkg_name}-git"
git clone $repo_url git clone "$repo_url"
if [ -d "$repo_dir" ] && [ -z "$(ls -A $repo_dir)" ]; then if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
echo "Repository $repo_dir is empty, trying without -git suffix" echo "Repository $repo_dir is empty, trying without -git suffix"
repo_url="https://aur.archlinux.org/${pkg_name}.git" repo_url="https://aur.archlinux.org/${pkg_name}.git"
repo_dir="${pkg_name}" repo_dir="${pkg_name}"
git clone $repo_url git clone "$repo_url"
if [ -d "$repo_dir" ] && [ -z "$(ls -A $repo_dir)" ]; then if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
echo "Repository $repo_dir is empty, trying to install with pacman" 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 echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
fi
else
if install_from_aur $repo_url $pkg_name; then
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
fi
fi
else else
if install_from_aur $repo_url $pkg_name; then echo "$pkg_name" >> failed.txt
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
fi
fi fi
done < "$file_path" else
if install_from_aur "$repo_url" "$pkg_name"; then
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
fi
fi
else
if install_from_aur "$repo_url" "$pkg_name"; then
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >> failed.txt
fi
fi
done < "$file_path"
} }
sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak
@ -124,147 +129,161 @@ sudo cp ./pacman.conf /etc/pacman.conf
# sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf # sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf
# mkinitcpio -P # mkinitcpio -P
# Reflector install / service management (idempotent & resilient) # 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" echo "reflector already installed"
else else
yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)" yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)"
fi fi
# Prefer timer over service (Arch default) # Prefer timer over service (Arch default)
if systemctl list-unit-files | grep -q '^reflector.timer'; then 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" echo "reflector.timer already enabled"
else else
sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer" sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer"
fi 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" echo "reflector.timer already active"
else else
if ! sudo systemctl start reflector.timer; then if ! sudo systemctl start reflector.timer; then
echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)" echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)"
fi
fi fi
fi
elif systemctl list-unit-files | grep -q '^reflector.service'; then 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" echo "reflector.service already enabled"
else else
sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service" sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service"
fi 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" echo "reflector.service already running"
else else
if ! sudo systemctl start reflector.service; then if ! sudo systemctl start reflector.service; then
echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)" echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)"
fi
fi fi
fi
else else
echo "reflector systemd unit not found (neither timer nor service)" echo "reflector systemd unit not found (neither timer nor service)"
fi 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 # Read pacman packages from file
declare -a pacman_packages declare -a pacman_packages
while IFS= read -r line; do while IFS= read -r line; do
# Skip empty lines and comments (lines not starting with alphanumeric characters) # 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") pacman_packages+=("$line")
fi fi
done < "pacman_packages.txt" done < "pacman_packages.txt"
for pkg in "${pacman_packages[@]}"; do for pkg in "${pacman_packages[@]}"; do
# Skip NVIDIA packages if GPU is not NVIDIA # Skip NVIDIA packages if GPU is not NVIDIA
if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then
echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)" echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)"
continue continue
fi fi
# Check for texlive subpackages # Check for texlive subpackages
if [ "$pkg" == "texlive" ]; then if [ "$pkg" == "texlive" ]; then
sub_pkgs=( sub_pkgs=(
texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra
texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities
texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience
texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks
texlive-publishers texlive-xetex texlive-publishers texlive-xetex
) )
all_installed=true all_installed=true
for subpkg in "${sub_pkgs[@]}"; do for subpkg in "${sub_pkgs[@]}"; do
if ! pacman -Qi "$subpkg" &> /dev/null; then if ! pacman -Qi "$subpkg" &> /dev/null; then
all_installed=false all_installed=false
break break
fi fi
done done
if [ "$all_installed" = true ]; then if [ "$all_installed" = true ]; then
echo "All texlive subpackages are installed, skipping texlive" echo "All texlive subpackages are installed, skipping texlive"
continue continue
fi
fi fi
fi
# Check for texlive-lang subpackages # Check for texlive-lang subpackages
if [ "$pkg" == "texlive-lang" ]; then if [ "$pkg" == "texlive-lang" ]; then
sub_pkgs=( sub_pkgs=(
texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic
texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench
texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese
texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese
texlive-langspanish texlive-langspanish
) )
all_installed=true all_installed=true
for subpkg in "${sub_pkgs[@]}"; do for subpkg in "${sub_pkgs[@]}"; do
if ! pacman -Qi "$subpkg" &> /dev/null; then if ! pacman -Qi "$subpkg" &> /dev/null; then
all_installed=false all_installed=false
break break
fi fi
done done
if [ "$all_installed" = true ]; then if [ "$all_installed" = true ]; then
echo "All texlive-lang subpackages are installed, skipping texlive-lang" echo "All texlive-lang subpackages are installed, skipping texlive-lang"
continue continue
fi
fi fi
fi
if ! pacman -Qi "$pkg" &> /dev/null; then if ! pacman -Qi "$pkg" &> /dev/null; then
if ! echo "${aur_packages[@]}" | grep -q "$pkg"; then if ! printf '%s
yes | sudo pacman -Sy --noconfirm "$pkg" ' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then
else yes | sudo pacman -Sy --noconfirm "$pkg"
echo "$pkg exists in AUR packages, skipping pacman installation"
fi
else else
echo "$pkg is already installed" echo "$pkg exists in AUR packages, skipping pacman installation"
fi fi
else
echo "$pkg is already installed"
fi
done done
if ! command -v nvm &> /dev/null; then if ! command -v nvm &> /dev/null; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
else else
echo "nvm is already installed" echo "nvm is already installed"
fi
export NVM_DIR="$HOME/.nvm"
if [ -s "$NVM_DIR/nvm.sh" ]; then
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
else
echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2
fi
if command -v nvm &> /dev/null; then
nvm i v18.20.5
nvm install --lts
else
echo "nvm command unavailable; skipping Node installation" >&2
fi fi
export NVM_DIR=$HOME/.nvm;
source $NVM_DIR/nvm.sh;
nvm i v18.20.5
nvm install --lts
sudo systemctl enable bluetooth.service sudo systemctl enable bluetooth.service
sudo systemctl start 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 for entry in "${aur_packages[@]}"; do
pkg_name=$(echo "$entry" | cut -d' ' -f1) pkg_name=${entry%% *}
repo_url=$(echo "$entry" | cut -d' ' -f2) repo_url=${entry#* }
install_from_aur "$repo_url" "$pkg_name" 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 done
cd ~/linux-configuration/fresh-install cd ~/linux-configuration/fresh-install
if [ ! -d "$HOME/.config/mpv" ]; then if [ ! -d "$HOME/.config/mpv" ]; then
mkdir -p "$HOME/.config/mpv" mkdir -p "$HOME/.config/mpv"
fi fi
cp mpv.conf "$HOME/.config/mpv/mpv.conf" cp mpv.conf "$HOME/.config/mpv/mpv.conf"
if [ ! -d "$HOME/.oh-my-zsh" ]; then if [ ! -d "$HOME/.oh-my-zsh" ]; then
yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
else else
echo "Oh My Zsh is already installed" echo "Oh My Zsh is already installed"
fi fi
cd ~/linux-configuration cd ~/linux-configuration

View File

@ -12,11 +12,11 @@
#-- The download utilities that makepkg should use to acquire sources #-- The download utilities that makepkg should use to acquire sources
# Format: 'protocol::agent' # Format: 'protocol::agent'
DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u' DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u'
'ftp::/usr/bin/curl -qgfC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u' 'ftp::/usr/bin/curl -qgfC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'
'http::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' 'http::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u'
'https::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u' 'https::/usr/bin/curl -qgb "" -fLC - --retry 3 --retry-delay 3 -o %o %u'
'rsync::/usr/bin/rsync --no-motd -z %u %o' 'rsync::/usr/bin/rsync --no-motd -z %u %o'
'scp::/usr/bin/scp -C %u %o') 'scp::/usr/bin/scp -C %u %o')
# Other common tools: # Other common tools:
# /usr/bin/snarf # /usr/bin/snarf
@ -26,10 +26,10 @@ DLAGENTS=('file::/usr/bin/curl -qgC - -o %o %u'
#-- The package required by makepkg to download VCS sources #-- The package required by makepkg to download VCS sources
# Format: 'protocol::package' # Format: 'protocol::package'
VCSCLIENTS=('bzr::breezy' VCSCLIENTS=('bzr::breezy'
'fossil::fossil' 'fossil::fossil'
'git::git' 'git::git'
'hg::mercurial' 'hg::mercurial'
'svn::subversion') 'svn::subversion')
######################################################################### #########################################################################
# ARCHITECTURE, COMPILE FLAGS # ARCHITECTURE, COMPILE FLAGS

View File

@ -9,24 +9,24 @@ TARGET="/etc/hosts"
LOG_FILE="/var/log/hosts-guard.log" LOG_FILE="/var/log/hosts-guard.log"
log() { log() {
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2 printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2
} }
if [[ ! -f "$CANONICAL_SOURCE" ]]; then if [[ ! -f $CANONICAL_SOURCE ]]; then
log "Canonical hosts not found at $CANONICAL_SOURCE; aborting enforcement" log "Canonical hosts not found at $CANONICAL_SOURCE; aborting enforcement"
exit 0 exit 0
fi fi
if ! cmp -s "$CANONICAL_SOURCE" "$TARGET"; then if ! cmp -s "$CANONICAL_SOURCE" "$TARGET"; then
log "Difference detected restoring $TARGET from canonical copy" log "Difference detected restoring $TARGET from canonical copy"
cp "$CANONICAL_SOURCE" "$TARGET" cp "$CANONICAL_SOURCE" "$TARGET"
chmod 644 "$TARGET" chmod 644 "$TARGET"
else else
log "No drift detected (contents identical)" log "No drift detected (contents identical)"
fi fi
# Re-apply protective attributes: immutable first, then read-only bind mount handled by separate unit # 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" chattr +i "$TARGET" || log "Failed to set immutable attribute"
log "Enforcement complete" log "Enforcement complete"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail 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 "$@" require_root "$@"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@ -10,7 +10,7 @@ HOOKS_DIR="/etc/pacman.d/hooks"
install -d -m 755 "$HOOKS_DIR" install -d -m 755 "$HOOKS_DIR"
# Pre-transaction hook # Pre-transaction hook
cat >"$HOOKS_DIR/10-unlock-etc-hosts.hook" <<'HOOK' cat > "$HOOKS_DIR/10-unlock-etc-hosts.hook" << 'HOOK'
[Trigger] [Trigger]
Operation = Upgrade Operation = Upgrade
Operation = Install Operation = Install
@ -26,7 +26,7 @@ NeedsTargets
HOOK HOOK
# Post-transaction hook # Post-transaction hook
cat >"$HOOKS_DIR/90-relock-etc-hosts.hook" <<'HOOK' cat > "$HOOKS_DIR/90-relock-etc-hosts.hook" << 'HOOK'
[Trigger] [Trigger]
Operation = Upgrade Operation = Upgrade
Operation = Install Operation = Install

View File

@ -5,22 +5,22 @@ TARGET=/etc/hosts
ENFORCE=/usr/local/sbin/enforce-hosts.sh ENFORCE=/usr/local/sbin/enforce-hosts.sh
LOGTAG=hosts-guard-hook 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() { collapse_mounts() {
local i=0 local i=0
if command -v mountpoint >/devnull 2>&1; then if command -v mountpoint > /devnull 2>&1; then
while mountpoint -q "$TARGET"; do while mountpoint -q "$TARGET"; do
umount -l "$TARGET" >/dev/null 2>&1 || break umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i+1)) i=$((i + 1))
(( i > 20 )) && break ((i > 20)) && break
done done
else else
local cnt local cnt
cnt=$(mount_layers_count) cnt=$(mount_layers_count)
while (( cnt > 1 )); do while ((cnt > 1)); do
umount -l "$TARGET" >/dev/null 2>&1 || break umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i+1)) i=$((i + 1))
(( i > 20 )) && break ((i > 20)) && break
cnt=$(mount_layers_count) cnt=$(mount_layers_count)
done done
fi fi
@ -28,23 +28,23 @@ collapse_mounts() {
# Ensure we end with a single read-only bind mount layer # Ensure we end with a single read-only bind mount layer
logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)" 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 collapse_mounts
if [[ -x "$ENFORCE" ]]; then if [[ -x $ENFORCE ]]; then
"$ENFORCE" >/dev/null 2>&1 || true "$ENFORCE" > /dev/null 2>&1 || true
fi fi
# Apply exactly one ro bind layer # Apply exactly one ro bind layer
mount --bind "$TARGET" "$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 mount -o remount,ro,bind "$TARGET" > /dev/null 2>&1 || true
# Start only the path watcher; avoid bind-mount service (we already bound once) # Start only the path watcher; avoid bind-mount service (we already bound once)
if command -v systemctl >/dev/null 2>&1; then if command -v systemctl > /dev/null 2>&1; then
systemctl start hosts-guard.path >/dev/null 2>&1 || true systemctl start hosts-guard.path > /dev/null 2>&1 || true
fi fi
logger -t "$LOGTAG" "post: relocking /etc/hosts (done)" 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 exit 0

View File

@ -7,58 +7,57 @@ LOGTAG=hosts-guard-hook
stop_units_if_present() { stop_units_if_present() {
local units=(hosts-bind-mount.service hosts-guard.path) local units=(hosts-bind-mount.service hosts-guard.path)
for u in "${units[@]}"; do for u in "${units[@]}"; do
if command -v systemctl >/dev/null 2>&1; then if command -v systemctl > /dev/null 2>&1; then
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then if systemctl list-unit-files 2> /dev/null | grep -q "^$u"; then
systemctl stop "$u" >/dev/null 2>&1 || true systemctl stop "$u" > /dev/null 2>&1 || true
fi fi
fi fi
done done
} }
is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; } 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; }
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() { cleanup_mount_stacks() {
local i=0 local i=0
# Unmount bind layers until /etc/hosts is no longer a mountpoint # 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 while mountpoint -q "$TARGET"; do
umount -l "$TARGET" >/dev/null 2>&1 || break umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i+1)) i=$((i + 1))
(( i > 20 )) && break ((i > 20)) && break
done done
else else
# Fallback to best-effort using mountinfo count # Fallback to best-effort using mountinfo count
local cnt local cnt
cnt=$(mount_layers_count) cnt=$(mount_layers_count)
while (( cnt > 1 )); do while ((cnt > 1)); do
umount -l "$TARGET" >/dev/null 2>&1 || break umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i+1)) i=$((i + 1))
(( i > 20 )) && break ((i > 20)) && break
cnt=$(mount_layers_count) cnt=$(mount_layers_count)
done done
fi fi
} }
# Drop protective attributes if present # Drop protective attributes if present
if command -v lsattr >/dev/null 2>&1; then if command -v lsattr > /dev/null 2>&1; then
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) 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 " i " && chattr -i "$TARGET" > /dev/null 2>&1 || true
echo "$attrs" | grep -q " a " && chattr -a "$TARGET" >/dev/null 2>&1 || true echo "$attrs" | grep -q " a " && chattr -a "$TARGET" > /dev/null 2>&1 || true
fi fi
stop_units_if_present stop_units_if_present
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)" 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 # Always collapse any existing layers; we'll operate on the plain file
cleanup_mount_stacks cleanup_mount_stacks
# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again # If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again
if is_ro_mount; then 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 fi
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)" logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)"

View File

@ -11,36 +11,36 @@ DELAY_SECONDS=45
log() { printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG" >&2; } 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 "$@" require_root "$@"
echo "Reason for editing /etc/hosts (will be logged):" >&2 echo "Reason for editing /etc/hosts (will be logged):" >&2
read -r -p "Enter reason: " REASON read -r -p "Enter reason: " REASON
if [[ -z ${REASON// } ]]; then if [[ -z ${REASON// /} ]]; then
echo "Empty reason not allowed. Aborting." >&2 echo "Empty reason not allowed. Aborting." >&2
exit 1 exit 1
fi fi
log "Requested intentional /etc/hosts modification session. Reason: $REASON" log "Requested intentional /etc/hosts modification session. Reason: $REASON"
logger -t "$SYSLOG_TAG" "session_start user=${SUDO_USER:-$USER} reason='$REASON'" logger -t "$SYSLOG_TAG" "session_start user=${SUDO_USER:-$USER} reason='$REASON'"
echo "This action is logged. A cooling-off delay of $DELAY_SECONDS seconds applies." >&2 echo "This action is logged. A cooling-off delay of $DELAY_SECONDS seconds applies." >&2
for s in hosts-bind-mount.service hosts-guard.path; do for s in hosts-bind-mount.service hosts-guard.path; do
if systemctl is-active --quiet "$s"; then if systemctl is-active --quiet "$s"; then
log "Stopping $s" log "Stopping $s"
systemctl stop "$s" || true systemctl stop "$s" || true
fi fi
if systemctl is-enabled --quiet "$s"; then if systemctl is-enabled --quiet "$s"; then
log "(Will re-enable later)" log "(Will re-enable later)"
fi fi
done done
# Remove attributes to allow edit # Remove attributes to allow edit
chattr -i -a "$TARGET" 2>/dev/null || true chattr -i -a "$TARGET" 2> /dev/null || true
echo "Countdown:" >&2 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 printf '\rEdit window opens in %2d seconds... Press Ctrl+C to abort.' "$i" >&2
sleep 1 sleep 1
done done
echo >&2 echo >&2
@ -49,13 +49,13 @@ sha_before=$(sha256sum "$TARGET" | awk '{print $1}')
"$EDITOR_CMD" "$TARGET" "$EDITOR_CMD" "$TARGET"
sha_after=$(sha256sum "$TARGET" | awk '{print $1}') 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" log "No changes made to $TARGET. Reason: $REASON"
logger -t "$SYSLOG_TAG" "no_change user=${SUDO_USER:-$USER} reason='$REASON'" logger -t "$SYSLOG_TAG" "no_change user=${SUDO_USER:-$USER} reason='$REASON'"
else else
log "Changes detected. Updating canonical copy and re-enforcing. Reason: $REASON" log "Changes detected. Updating canonical copy and re-enforcing. Reason: $REASON"
logger -t "$SYSLOG_TAG" "modified user=${SUDO_USER:-$USER} reason='$REASON'" logger -t "$SYSLOG_TAG" "modified user=${SUDO_USER:-$USER} reason='$REASON'"
cp "$TARGET" "$CANON" cp "$TARGET" "$CANON"
fi fi
# Re-run enforcement # Re-run enforcement

View File

@ -46,10 +46,20 @@ ADD_ALIAS_STUB=1
msg() { printf '\e[1;32m[+]\e[0m %s\n' "$*"; } msg() { printf '\e[1;32m[+]\e[0m %s\n' "$*"; }
note() { printf '\e[1;34m[i]\e[0m %s\n' "$*"; } note() { printf '\e[1;34m[i]\e[0m %s\n' "$*"; }
warn() { printf '\e[1;33m[!]\e[0m %s\n' "$*"; } warn() { printf '\e[1;33m[!]\e[0m %s\n' "$*"; }
err() { printf '\e[1;31m[x]\e[0m %s\n' "$*" >&2; } 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\}//'; } 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 while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--force-snapshot) FORCE_SNAPSHOT=1 ; shift ;; --force-snapshot)
--no-snapshot) DO_SNAPSHOT=0 ; shift ;; FORCE_SNAPSHOT=1
--skip-bind) ENABLE_BIND=0 ; shift ;; shift
--skip-path-watch) ENABLE_PATH=0 ; shift ;; ;;
--delay) DELAY=${2:-} ; [[ -z ${DELAY} ]] && { err '--delay requires value'; exit 2; } ; shift 2 ;; --no-snapshot)
--dry-run) DRY_RUN=1 ; shift ;; DO_SNAPSHOT=0
--no-shell-hooks) INSTALL_SHELL_HOOKS=0 ; shift ;; shift
--shell-hooks) INSTALL_SHELL_HOOKS=1 ; shift ;; ;;
--no-audit) INSTALL_AUDIT_RULE=0 ; shift ;; --skip-bind)
--audit) INSTALL_AUDIT_RULE=1 ; shift ;; ENABLE_BIND=0
--no-alias-stub) ADD_ALIAS_STUB=0 ; shift ;; shift
--alias-stub) ADD_ALIAS_STUB=1 ; shift ;; ;;
--uninstall) UNINSTALL=1 ; shift ;; --skip-path-watch)
-h|--help) usage; exit 0 ;; ENABLE_PATH=0
*) err "Unknown argument: $1"; usage; exit 2 ;; 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 esac
done done
@ -119,7 +179,7 @@ if [[ $UNINSTALL -eq 1 ]]; then
"$SYSTEMD_DIR/hosts-bind-mount.service" \ "$SYSTEMD_DIR/hosts-bind-mount.service" \
"$ZSH_FILTER_SNIPPET" \ "$ZSH_FILTER_SNIPPET" \
"$BASH_FILTER_SNIPPET"; do "$BASH_FILTER_SNIPPET"; do
if [[ -e $f ]]; then run rm -f "$f"; fi if [[ -e $f ]]; then run rm -f "$f"; fi
done done
note "Leaving canonical snapshot at $CANON (remove manually if undesired)." note "Leaving canonical snapshot at $CANON (remove manually if undesired)."
if [[ $DRY_RUN -eq 0 ]]; then systemctl daemon-reload; fi if [[ $DRY_RUN -eq 0 ]]; then systemctl daemon-reload; fi
@ -134,10 +194,13 @@ note "Script directory: $SCRIPT_DIR"
note "Repository root: $REPO_ROOT" note "Repository root: $REPO_ROOT"
for req in "$TEMPLATE_ENFORCE" "$TEMPLATE_UNLOCK" "$UNIT_GUARD_SERVICE"; do 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 done
if [[ ! -f "$HOSTS" ]]; then if [[ ! -f $HOSTS ]]; then
err "$HOSTS does not exist. Run your hosts/install.sh first." err "$HOSTS does not exist. Run your hosts/install.sh first."
exit 1 exit 1
fi fi
@ -146,7 +209,7 @@ fi
# Snapshot # Snapshot
###################################################################### ######################################################################
if [[ $DO_SNAPSHOT -eq 1 ]]; then 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)" note "Canonical snapshot exists (use --force-snapshot to overwrite)"
else else
msg "Creating canonical snapshot at $CANON" msg "Creating canonical snapshot at $CANON"
@ -182,14 +245,12 @@ fi
if [[ $INSTALL_SHELL_HOOKS -eq 1 ]]; then if [[ $INSTALL_SHELL_HOOKS -eq 1 ]]; then
msg "Installing shell history suppression hooks for unlock command" msg "Installing shell history suppression hooks for unlock command"
# Pattern matches commands invoking unlock-hosts (with or without sudo) & setup script force snapshot # 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 # 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 if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY-RUN: would create $ZSH_FILTER_SNIPPET" echo "DRY-RUN: would create $ZSH_FILTER_SNIPPET"
else else
cat > "$ZSH_FILTER_SNIPPET" <<'ZEOF' cat > "$ZSH_FILTER_SNIPPET" << 'ZEOF'
# Added by hosts guard setup suppress unlock-hosts commands from Zsh history # Added by hosts guard setup suppress unlock-hosts commands from Zsh history
autoload -Uz add-zsh-hook 2>/dev/null || true autoload -Uz add-zsh-hook 2>/dev/null || true
_hosts_guard_history_filter() { _hosts_guard_history_filter() {
@ -213,11 +274,11 @@ ZEOF
fi fi
# Bash: rely on HISTCONTROL and PROMPT_COMMAND filter # 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 if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY-RUN: would create $BASH_FILTER_SNIPPET" echo "DRY-RUN: would create $BASH_FILTER_SNIPPET"
else else
cat > "$BASH_FILTER_SNIPPET" <<'BEOF' cat > "$BASH_FILTER_SNIPPET" << 'BEOF'
# Added by hosts guard setup suppress unlock-hosts commands from Bash history # Added by hosts guard setup suppress unlock-hosts commands from Bash history
export HISTCONTROL=ignoredups:erasedups export HISTCONTROL=ignoredups:erasedups
_hosts_guard_hist_filter() { _hosts_guard_hist_filter() {
@ -253,7 +314,7 @@ if [[ $ADD_ALIAS_STUB -eq 1 ]]; then
if [[ $DRY_RUN -eq 1 ]]; then if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY-RUN: would create $PROFILE_STUB" echo "DRY-RUN: would create $PROFILE_STUB"
else else
cat > "$PROFILE_STUB" <<'ASTUB' cat > "$PROFILE_STUB" << 'ASTUB'
# Added by hosts guard setup discourages casual use of unlock-hosts name # Added by hosts guard setup discourages casual use of unlock-hosts name
if command -v unlock-hosts >/dev/null 2>&1; then 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)"' 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) # Audit rule to record executions (requires auditd)
###################################################################### ######################################################################
if [[ $INSTALL_AUDIT_RULE -eq 1 ]]; then if [[ $INSTALL_AUDIT_RULE -eq 1 ]]; then
if command -v auditctl >/dev/null 2>&1; then if command -v auditctl > /dev/null 2>&1; then
AUDIT_RULE="-w /usr/local/sbin/unlock-hosts -p x -k hosts_unlock" audit_rule_str="-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 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" note "Audit rule already present"
else 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 if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY-RUN: would create /etc/audit/rules.d/hosts_unlock.rules" echo "DRY-RUN: would create /etc/audit/rules.d/hosts_unlock.rules"
else else
echo "$AUDIT_RULE" > /etc/audit/rules.d/hosts_unlock.rules echo "$audit_rule_str" > /etc/audit/rules.d/hosts_unlock.rules
fi fi
fi fi
else else

View File

@ -2,7 +2,7 @@
# Re-run with sudo if not root # Re-run with sudo if not root
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
exec sudo -E bash "$0" "$@" exec sudo -E bash "$0" "$@"
fi fi
# Options # Options
@ -11,25 +11,25 @@ FLUSH_DNS=0
# Parse CLI flags # Parse CLI flags
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--flush-dns) --flush-dns)
FLUSH_DNS=1 FLUSH_DNS=1
;; ;;
--no-flush-dns) --no-flush-dns)
FLUSH_DNS=0 FLUSH_DNS=0
;; ;;
-h|--help) -h | --help)
echo "Usage: $0 [--flush-dns|--no-flush-dns]" echo "Usage: $0 [--flush-dns|--no-flush-dns]"
exit 0 exit 0
;; ;;
esac esac
done done
# Enable systemd-resolved # Enable systemd-resolved
sudo systemctl enable systemd-resolved sudo systemctl enable systemd-resolved
# Remove all attributes from /etc/hosts to allow modifications # 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 # Source and local cache configuration
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts" URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
@ -38,33 +38,33 @@ LOCAL_CACHE="/etc/hosts.stevenblack"
# Helpers # Helpers
extract_date_epoch_from_file() { extract_date_epoch_from_file() {
# Grep "# Date:" line and convert to epoch seconds (UTC) # Grep "# Date:" line and convert to epoch seconds (UTC)
local f="$1" local f="$1"
local line local line
line=$(grep -m1 '^# Date:' "$f" 2>/dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/') line=$(grep -m1 '^# Date:' "$f" 2> /dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/')
if [[ -n "$line" ]]; then if [[ -n $line ]]; then
date -u -d "$line" +%s 2>/dev/null || echo "" date -u -d "$line" +%s 2> /dev/null || echo ""
else else
echo "" echo ""
fi fi
} }
fetch_remote_header() { fetch_remote_header() {
# Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head # Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head
local out="$1" local out="$1"
if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then
return 0 return 0
fi fi
# Fallback may download more, but we only keep first lines # Fallback may download more, but we only keep first lines
if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then
return 0 return 0
fi fi
return 1 return 1
} }
download_remote_full_to() { download_remote_full_to() {
local out="$1" local out="$1"
curl -LfsS "$URL" -o "$out" curl -LfsS "$URL" -o "$out"
} }
# Decide whether to use cache or update # Decide whether to use cache or update
@ -73,50 +73,47 @@ trap 'rm -f "$TMP_REMOTE_HEAD"' EXIT
REMOTE_AVAILABLE=0 REMOTE_AVAILABLE=0
if fetch_remote_header "$TMP_REMOTE_HEAD"; then if fetch_remote_header "$TMP_REMOTE_HEAD"; then
REMOTE_AVAILABLE=1 REMOTE_AVAILABLE=1
fi fi
USE_CACHE=0
NEED_UPDATE=0 NEED_UPDATE=0
if [[ -f "$LOCAL_CACHE" ]]; then if [[ -f $LOCAL_CACHE ]]; then
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE") local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
else else
local_epoch="" local_epoch=""
fi fi
if [[ $REMOTE_AVAILABLE -eq 1 ]]; then if [[ $REMOTE_AVAILABLE -eq 1 ]]; then
remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD") 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)." echo "Using cached StevenBlack hosts (up-to-date)."
USE_CACHE=1 else
else echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..."
echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..." NEED_UPDATE=1
NEED_UPDATE=1 fi
fi
else else
if [[ -f "$LOCAL_CACHE" ]]; then if [[ -f $LOCAL_CACHE ]]; then
echo "No internet; using cached StevenBlack hosts." echo "No internet; using cached StevenBlack hosts."
USE_CACHE=1 else
else echo "Error: No internet and no cached StevenBlack hosts found." >&2
echo "Error: No internet and no cached StevenBlack hosts found." >&2 exit 1
exit 1 fi
fi
fi fi
# Ensure we have a fresh cache if needed # Ensure we have a fresh cache if needed
if [[ $NEED_UPDATE -eq 1 ]]; then if [[ $NEED_UPDATE -eq 1 ]]; then
TMP_DL=$(mktemp) TMP_DL=$(mktemp)
if download_remote_full_to "$TMP_DL"; then if download_remote_full_to "$TMP_DL"; then
# Save raw upstream to cache # Save raw upstream to cache
sudo mv "$TMP_DL" "$LOCAL_CACHE" sudo mv "$TMP_DL" "$LOCAL_CACHE"
sudo chmod 644 "$LOCAL_CACHE" sudo chmod 644 "$LOCAL_CACHE"
echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE" echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE"
else else
rm -f "$TMP_DL" rm -f "$TMP_DL"
echo "Error: Failed to download latest StevenBlack hosts." >&2 echo "Error: Failed to download latest StevenBlack hosts." >&2
exit 1 exit 1
fi fi
fi fi
# Install the base hosts from cache into /etc/hosts # Install the base hosts from cache into /etc/hosts
@ -265,10 +262,10 @@ sudo chattr -i /etc/hosts
sudo chattr +a /etc/hosts sudo chattr +a /etc/hosts
# Optionally flush DNS caches # Optionally flush DNS caches
if [[ "$FLUSH_DNS" -eq 1 ]]; then if [[ $FLUSH_DNS -eq 1 ]]; then
echo "Flushing DNS caches..." echo "Flushing DNS caches..."
sudo systemd-resolve --flush-caches sudo systemd-resolve --flush-caches
sudo systemctl restart NetworkManager.service sudo systemctl restart NetworkManager.service
else else
echo "DNS cache flush skipped (use --flush-dns to enable)." echo "DNS cache flush skipped (use --flush-dns to enable)."
fi fi

View File

@ -4,45 +4,45 @@
# Check if ActivityWatch is installed # Check if ActivityWatch is installed
check_installed() { check_installed() {
# Check if activitywatch-bin package is 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 return 0
fi fi
# Check if aw-qt binary exists # Check if aw-qt binary exists
if command -v aw-qt &>/dev/null; then if command -v aw-qt &> /dev/null; then
return 0 return 0
fi fi
return 1 return 1
} }
# Check if ActivityWatch is running # Check if ActivityWatch is running
check_running() { check_running() {
# Check for aw-qt process # 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 return 0
fi fi
# Check for aw-server process # 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 return 0
fi fi
return 1 return 1
} }
# Main logic # Main logic
if ! check_installed; then if ! check_installed; then
echo "AW uninstalled" echo "AW uninstalled"
echo echo
echo "#FF0000" # Red echo "#FF0000" # Red
elif check_running; then elif check_running; then
echo "AW on" echo "AW on"
echo echo
echo "#00FF00" # Green echo "#00FF00" # Green
else else
echo "AW off" echo "AW off"
echo echo
echo "#FF0000" # Red echo "#FF0000" # Red
fi fi

View File

@ -5,11 +5,10 @@ bluetooth_info=$(bluetoothctl info)
# Check if Bluetooth is connected # Check if Bluetooth is connected
if echo "$bluetooth_info" | grep -q "Connected: yes"; then if echo "$bluetooth_info" | grep -q "Connected: yes"; then
device=$(echo "$bluetooth_info" | grep "Alias" | cut -d ' ' -f2-) device=$(echo "$bluetooth_info" | grep "Alias" | cut -d ' ' -f2-)
echo "$device" #  is the Bluetooth icon echo "$device" #  is the Bluetooth icon
echo echo
echo "#50FA7B" # Green for connected echo "#50FA7B" # Green for connected
else else
echo " Disconnected" echo " Disconnected"
fi fi

View File

@ -3,46 +3,46 @@
# CPU Temperature # CPU Temperature
cpu_temp=$(sensors | awk '/^Tctl:/ {print $2}' | tr -d '+°C') cpu_temp=$(sensors | awk '/^Tctl:/ {print $2}' | tr -d '+°C')
if [ -z "$cpu_temp" ]; then if [ -z "$cpu_temp" ]; then
cpu_temp=$(sensors | awk '/^Package id 0:/ {print $4}' | tr -d '+°C') cpu_temp=$(sensors | awk '/^Package id 0:/ {print $4}' | tr -d '+°C')
fi fi
if [ -z "$cpu_temp" ]; then if [ -z "$cpu_temp" ]; then
cpu_temp=$(sensors | awk '/^Core 0:/ {print $3}' | tr -d '+°C') cpu_temp=$(sensors | awk '/^Core 0:/ {print $3}' | tr -d '+°C')
fi fi
if [ -z "$cpu_temp" ]; then if [ -z "$cpu_temp" ]; then
cpu_temp="N/A" cpu_temp="N/A"
fi fi
# CPU Load (1-minute average) # CPU Load (1-minute average)
cpu_load=$(awk '{print $1}' /proc/loadavg) cpu_load=$(awk '{print $1}' /proc/loadavg)
if [ -z "$cpu_load" ]; then if [ -z "$cpu_load" ]; then
cpu_load="N/A" cpu_load="N/A"
fi fi
# Colors for CPU Load and Temperature # Colors for CPU Load and Temperature
cpu_color="#FFFFFF" # Default color cpu_color="#FFFFFF" # Default color
# Change color based on CPU load # 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)}') 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 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 cpu_color="#F1FA8C" # Yellow for medium load
else else
cpu_color="#FF5555" # Red for high load cpu_color="#FF5555" # Red for high load
fi fi
fi fi
# Change color based on CPU temperature # 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)}') 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 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 cpu_color="#F1FA8C" # Yellow for medium temperature
else else
cpu_color="#FF5555" # Red for high temperature cpu_color="#FF5555" # Red for high temperature
fi fi
fi fi
echo -e "<span color=\"$cpu_color\"> ${cpu_temp}°C, ${cpu_load}</span>" echo -e "<span color=\"$cpu_color\"> ${cpu_temp}°C, ${cpu_load}</span>"

View File

@ -2,41 +2,41 @@
# Function to get NVIDIA GPU metrics # Function to get NVIDIA GPU metrics
get_nvidia_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 if [ -z "$gpu_temp" ]; then
gpu_temp="N/A" gpu_temp="N/A"
fi 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 if [ -z "$gpu_load" ]; then
gpu_load="N/A" gpu_load="N/A"
fi fi
echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load"
} }
# Function to get Intel GPU metrics # Function to get Intel GPU metrics
get_intel_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 if [ -z "$gpu_load" ]; then
gpu_load="N/A" gpu_load="N/A"
fi fi
gpu_temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C') gpu_temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C')
if [ -z "$gpu_temp" ]; then if [ -z "$gpu_temp" ]; then
gpu_temp="N/A" gpu_temp="N/A"
fi fi
echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load" echo "GPU Temp: $gpu_temp°C, GPU Load: $gpu_load"
} }
# Detect GPU type and get metrics # Detect GPU type and get metrics
if lspci | grep -i nvidia > /dev/null; then if lspci | grep -i nvidia > /dev/null; then
gpu_metrics=$(get_nvidia_metrics) gpu_metrics=$(get_nvidia_metrics)
elif lspci | grep -i vga | grep -i intel > /dev/null; then elif lspci | grep -i vga | grep -i intel > /dev/null; then
gpu_metrics=$(get_intel_metrics) gpu_metrics=$(get_intel_metrics)
else else
echo "No supported GPU found." echo "No supported GPU found."
fi fi
#!/bin/bash #!/bin/bash
@ -46,20 +46,19 @@ gpu_load=$(echo "$gpu_metrics" | awk -F', ' '{print $2}' | awk -F': ' '{print $2
gpu_color="#FFFFFF" gpu_color="#FFFFFF"
# Colors for GPU Load # Colors for GPU Load
if [[ "$gpu_load" != "N/A" ]]; then if [[ $gpu_load != "N/A" ]]; then
if (( $(echo "$gpu_load < 50.0" | bc -l) )); then if (($(echo "$gpu_load < 50.0" | bc -l))); then
gpu_color="#50FA7B" # Green 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 gpu_color="#F1FA8C" # Yellow
else else
gpu_color="#FF5555" # Red gpu_color="#FF5555" # Red
fi fi
else else
gpu_color="#FFFFFF" # Default color gpu_color="#FFFFFF" # Default color
fi fi
# Output< # Output<
echo -e "<span color=\"$gpu_color\"> ${gpu_temp}, ${gpu_load}%</span>" echo -e "<span color=\"$gpu_color\"> ${gpu_temp}, ${gpu_load}%</span>"
echo echo
echo "#FFFFFF" # Default color for fallback (ignored if markup is enabled) echo "#FFFFFF" # Default color for fallback (ignored if markup is enabled)

View File

@ -4,24 +4,23 @@
temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C') temp=$(sensors | awk '/^temp1:/ {print $2; exit}' | tr -d '+°C')
# Ensure the temperature is a valid number # 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 " MB: N/A"
echo echo
echo "#FF5555" # Red color for error echo "#FF5555" # Red color for error
exit 1 exit 1
fi fi
# Define temperature thresholds # 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 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 color="#F1FA8C" # Yellow for warning temperature
else else
color="#FF5555" # Red for high temperature color="#FF5555" # Red for high temperature
fi fi
# Output the temperature with the color # Output the temperature with the color
echo "${temp}°C" #  is a thermometer icon echo "${temp}°C" #  is a thermometer icon
echo echo
echo $color echo $color

View File

@ -2,23 +2,28 @@
# Function to detect all active network interfaces # Function to detect all active network interfaces
detect_interfaces() { detect_interfaces() {
interfaces=() local iface_path iface state
for interface in /sys/class/net/*; do for iface_path in /sys/class/net/*; do
interface=$(basename "$interface") iface=$(basename "$iface_path")
if [[ "$interface" != "lo" && -d "/sys/class/net/$interface" && "$(cat /sys/class/net/$interface/operstate)" == "up" ]]; then if [[ $iface == "lo" || ! -d "/sys/class/net/$iface" ]]; then
interfaces+=("$interface") continue
fi fi
done if [[ -r "/sys/class/net/$iface/operstate" ]]; then
echo "${interfaces[@]}" state=$(< "/sys/class/net/$iface/operstate")
if [[ $state == "up" ]]; then
printf '%s\n' "$iface"
fi
fi
done
} }
# Detect all active network interfaces # Detect all active network interfaces
interfaces=$(detect_interfaces) mapfile -t interfaces < <(detect_interfaces)
# If no active interfaces are found, exit # If no active interfaces are found, exit
if [ -z "$interfaces" ]; then if [ "${#interfaces[@]}" -eq 0 ]; then
echo "No active network interfaces found" echo "No active network interfaces found"
exit 1 exit 1
fi fi
# Initialize total RX and TX bytes # Initialize total RX and TX bytes
@ -34,45 +39,49 @@ current_time=$(date +%s)
last_time=$current_time last_time=$current_time
# Iterate over each interface and accumulate RX and TX bytes # 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" rx_path="/sys/class/net/$interface/statistics/rx_bytes"
tx_path="/sys/class/net/$interface/statistics/tx_bytes" tx_path="/sys/class/net/$interface/statistics/tx_bytes"
rx_now=$(cat $rx_path 2>/dev/null) if ! read -r rx_now < "$rx_path"; then
tx_now=$(cat $tx_path 2>/dev/null) rx_now=0
fi
if ! read -r tx_now < "$tx_path"; then
tx_now=0
fi
state_file="/tmp/network_monitor_$interface" state_file="/tmp/network_monitor_$interface"
if [ -f "$state_file" ]; then 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 else
last_rx=$rx_now last_rx=$rx_now
last_tx=$tx_now last_tx=$tx_now
fi fi
total_rx_now=$((total_rx_now + rx_now)) total_rx_now=$((total_rx_now + rx_now))
total_tx_now=$((total_tx_now + tx_now)) total_tx_now=$((total_tx_now + tx_now))
total_last_rx=$((total_last_rx + last_rx)) total_last_rx=$((total_last_rx + last_rx))
total_last_tx=$((total_last_tx + last_tx)) total_last_tx=$((total_last_tx + last_tx))
# Save current RX and TX bytes for the next check # Save current RX and TX bytes for the next check
echo "$rx_now $tx_now $current_time" > "$state_file" echo "$rx_now $tx_now $current_time" > "$state_file"
done done
# Calculate time difference # Calculate time difference
time_diff=$((current_time - last_time)) time_diff=$((current_time - last_time))
# Calculate total download and upload speeds in bytes per second # Calculate total download and upload speeds in bytes per second
if (( time_diff > 0 )); then if ((time_diff > 0)); then
total_rx_rate=$(( (total_rx_now - total_last_rx) / time_diff )) total_rx_rate=$(((total_rx_now - total_last_rx) / time_diff))
total_tx_rate=$(( (total_tx_now - total_last_tx) / time_diff )) total_tx_rate=$(((total_tx_now - total_last_tx) / time_diff))
else else
total_rx_rate=0 total_rx_rate=0
total_tx_rate=0 total_tx_rate=0
fi fi
# Convert speeds to human-readable format # Convert speeds to human-readable format
rx_rate_human=$(numfmt --to=iec --suffix=B/s --padding=8 $total_rx_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) 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 # Store the result of printf into a string and echo it
printf " %s  %s\n" "$rx_rate_human" "$tx_rate_human" printf " %s  %s\n" "$rx_rate_human" "$tx_rate_human"

View File

@ -4,65 +4,69 @@
# Function to check if today is a monitored day # Function to check if today is a monitored day
is_monitored_day() { is_monitored_day() {
local day_of_week=$(date +%u) local day_of_week
if [[ "$day_of_week" == "1" ]] || [[ "$day_of_week" == "5" ]] || [[ "$day_of_week" == "6" ]] || [[ "$day_of_week" == "7" ]]; then day_of_week=$(date +%u)
return 0 if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then
else return 0
return 1 else
fi return 1
fi
} }
# Function to check if current time is in window # Function to check if current time is in window
is_current_time_in_window() { is_current_time_in_window() {
local current_hour=$(date +%H) local current_hour current_hour_num
local current_hour_num=$((10#$current_hour)) current_hour=$(date +%H)
if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then current_hour_num=$((10#$current_hour))
return 0 if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
else return 0
return 1 else
fi return 1
fi
} }
# Function to check if PC was booted in window today # Function to check if PC was booted in window today
was_booted_in_window_today() { was_booted_in_window_today() {
local today=$(date +%Y-%m-%d) local today uptime_seconds boot_time boot_date
local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") today=$(date +%Y-%m-%d)
local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
local boot_date=$(echo "$boot_time" | cut -d' ' -f1) 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 return 1
fi fi
local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) local boot_hour boot_hour_num
local boot_hour_num=$((10#$boot_hour)) 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 if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then
return 0 return 0
else else
return 1 return 1
fi fi
} }
# Main logic # Main logic
if ! is_monitored_day; then if ! is_monitored_day; then
# Not a monitored day # Not a monitored day
echo "PC:skip" echo "PC:skip"
echo echo
echo "#888888" # Gray echo "#888888" # Gray
elif is_current_time_in_window; then elif is_current_time_in_window; then
# Currently in the window - all good # Currently in the window - all good
echo "PC:live" echo "PC:live"
echo echo
echo "#00FF00" # Green echo "#00FF00" # Green
elif was_booted_in_window_today; then elif was_booted_in_window_today; then
# Was booted in window today - compliant # Was booted in window today - compliant
echo "PC:ok" echo "PC:ok"
echo echo
echo "#00FF00" # Green echo "#00FF00" # Green
else else
# Was NOT booted in window today - non-compliant # Was NOT booted in window today - non-compliant
echo "PC:warn" echo "PC:warn"
echo echo
echo "#FF0000" # Red echo "#FF0000" # Red
fi fi

View File

@ -1,20 +1,19 @@
#!/bin/bash #!/bin/bash
# Get the current volume level and mute status # Get the current volume level and mute status
volume=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | tr -d '%') volume=$(pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}' | tr -d '%')
mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}') mute=$(pactl get-sink-mute @DEFAULT_SINK@ | awk '{print $2}')
color="#50FA7B"
# Determine icon and color based on mute status # Determine icon and color based on mute status
if [ "$mute" = "yes" ]; then if [ "$mute" = "yes" ]; then
icon="🔇" # Muted icon="🔇" # Muted
color="#FF5555"
else else
icon="🔊" # Volume icon icon="🔊" # Volume icon
color="#50FA7B" # Green
fi fi
# Output the volume with icon and color # Output the volume with icon and color
echo "$icon $volume%" echo "$icon $volume%"
echo echo
echo $color echo "$color"

View File

@ -2,25 +2,25 @@
# Check if warp-cli is installed # Check if warp-cli is installed
if ! command -v warp-cli &> /dev/null; then if ! command -v warp-cli &> /dev/null; then
echo " N/A" echo " N/A"
exit 0 exit 0
fi fi
# Get the status from warp-cli # 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 # Display the status with an icon
if [ "$status" = "Connected" ]; then if [ "$status" = "Connected" ]; then
echo "🔒 !!! WARP CONNECTED !!!" echo "🔒 !!! WARP CONNECTED !!!"
echo echo
echo "#FFFF00" # Yellow echo "#FFFF00" # Yellow
elif [ "$status" = "Disconnected" ]; then elif [ "$status" = "Disconnected" ]; then
echo "WARP disconnected" echo "WARP disconnected"
echo echo
echo "#00FF00" # Green echo "#00FF00" # Green
else else
echo "⚠️ ! WARP unknown !" echo "⚠️ ! WARP unknown !"
echo echo
echo "#FF0000" # Red echo "#FF0000" # Red
exit 0 exit 0
fi fi

View File

@ -5,23 +5,23 @@ wifi_interface=$(iw dev | awk '$1=="Interface"{print $2}')
# If no WiFi interface is found, exit # If no WiFi interface is found, exit
if [ -z "$wifi_interface" ]; then if [ -z "$wifi_interface" ]; then
echo " down" echo " down"
exit 1 exit 1
fi fi
# Get the WiFi details # 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 # Extract the SSID and signal strength
ssid=$(echo "$wifi_info" | awk -F '"' '/ESSID/ {print $2}') 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 # 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 # Output the result
if [ -z "$ssid" ]; then if [ -z "$ssid" ]; then
echo " down" echo " down"
else else
echo "$ssid ($signal dBm) $ip_address" echo "$ssid ($signal dBm) $ip_address"
fi fi

View File

@ -2,40 +2,40 @@
# Function to detect if the system is Ubuntu # Function to detect if the system is Ubuntu
is_ubuntu() { is_ubuntu() {
[ -f /etc/os-release ] && grep -qi 'ubuntu' /etc/os-release [ -f /etc/os-release ] && grep -qi 'ubuntu' /etc/os-release
} }
# Function to detect screen resolution and set font size # Function to detect screen resolution and set font size
set_font_size() { set_font_size() {
resolution=$(xdpyinfo | grep dimensions | awk '{print $2}') 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: # 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 # Icons (like for slack) are too small and i3blocks are too big
# Network monitor jumping becomes annoying # Network monitor jumping becomes annoying
if [ "$width" -gt 1920 ]; then if [ "$width" -gt 1920 ]; then
echo "8" echo "8"
else else
echo "8" echo "8"
fi fi
} }
# Check if Intel GPU is detected # Check if Intel GPU is detected
if lspci | grep -i 'vga' | grep -i 'intel'; then if lspci | grep -i 'vga' | grep -i 'intel'; then
if is_ubuntu; then if is_ubuntu; then
sudo apt-get update sudo apt-get update
sudo apt-get install -y intel-gpu-tools sudo apt-get install -y intel-gpu-tools
sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top
else else
yes | sudo pacman -S --needed intel-gpu-tools yes | sudo pacman -S --needed intel-gpu-tools
sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top sudo setcap cap_perfmon+ep /usr/bin/intel_gpu_top
fi fi
fi fi
if is_ubuntu; then if is_ubuntu; then
sudo apt-get update sudo apt-get update
sudo apt-get install -y fonts-dejavu-core fonts-noto fonts-font-awesome bc jq iw pulseaudio-utils sudo apt-get install -y fonts-dejavu-core fonts-noto fonts-font-awesome bc jq iw pulseaudio-utils
else else
yes | sudo pacman -S --needed ttf-dejavu noto-fonts ttf-font-awesome bc jq iw acpi yes | sudo pacman -S --needed ttf-dejavu noto-fonts ttf-font-awesome bc jq iw acpi
fi fi
# Set font size based on screen resolution # Set font size based on screen resolution

View File

@ -12,17 +12,17 @@ SCRIPT_NAME=${0##*/}
info() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; } info() { printf "\033[1;34m[INFO]\033[0m %s\n" "$*"; }
warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; }
require_cmd() { require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then if ! command -v "$1" > /dev/null 2>&1; then
err "Missing dependency: $1" err "Missing dependency: $1"
MISSING=1 MISSING=1
fi fi
} }
usage() { usage() {
cat <<EOF cat << EOF
${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub ${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub
Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox] Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox]
@ -46,15 +46,26 @@ AUTO_FIREFOX=0
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--version) --version)
VERSION="$2"; shift 2;; VERSION="$2"
shift 2
;;
--force) --force)
FORCE=1; shift;; FORCE=1
shift
;;
--install-firefox) --install-firefox)
AUTO_FIREFOX=1; shift;; AUTO_FIREFOX=1
-h|--help) shift
usage; exit 0;; ;;
-h | --help)
usage
exit 0
;;
*) *)
err "Unknown argument: $1"; usage; exit 2;; err "Unknown argument: $1"
usage
exit 2
;;
esac esac
done done
@ -65,42 +76,48 @@ require_cmd tar
require_cmd find require_cmd find
require_cmd sed require_cmd sed
require_cmd awk 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." warn "jq not found — will fall back to a simpler tag detection method."
fi 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_OWNER="proginosko"
REPO_NAME="LeechBlockNG" REPO_NAME="LeechBlockNG"
get_latest_tag() { get_latest_tag() {
local 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) 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 if [[ -n $tag && $tag != "null" ]]; then
echo "$tag"; return 0 echo "$tag"
return 0
fi fi
fi fi
# Fallback: follow redirect for /releases/latest to extract tag # 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) 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 if [[ -n $tag ]]; then
echo "$tag"; return 0 echo "$tag"
return 0
fi fi
return 1 return 1
} }
if [[ -z "$VERSION" ]]; then if [[ -z $VERSION ]]; then
info "Resolving latest release tag from GitHub…" info "Resolving latest release tag from GitHub…"
if ! VERSION=$(get_latest_tag); then 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
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." warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway."
fi fi
VERSION=${VERSION#v} # strip leading v for folder names VERSION=${VERSION#v} # strip leading v for folder names
TAG="v${VERSION}" TAG="v${VERSION}"
XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"} XDG_DATA_HOME=${XDG_DATA_HOME:-"$HOME/.local/share"}
@ -108,7 +125,7 @@ INSTALL_ROOT="$XDG_DATA_HOME/leechblockng"
VERSION_DIR="$INSTALL_ROOT/$VERSION" VERSION_DIR="$INSTALL_ROOT/$VERSION"
CURRENT_LINK="$INSTALL_ROOT/current" 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)." info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)."
else else
info "Downloading LeechBlockNG $TAG source from GitHub…" info "Downloading LeechBlockNG $TAG source from GitHub…"
@ -122,11 +139,14 @@ else
tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract" tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract"
# The archive usually extracts to REPO_NAME-TAG/ … # The archive usually extracts to REPO_NAME-TAG/ …
src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true) 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) # 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) 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." err "manifest.json not found in the extracted archive. The project layout may have changed."
exit 1 exit 1
fi fi
@ -137,30 +157,30 @@ else
info "Installing to $VERSION_DIR" info "Installing to $VERSION_DIR"
mkdir -p "$VERSION_DIR" mkdir -p "$VERSION_DIR"
# Copy the extension directory as-is (avoid bringing tests or build scripts) # 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" ln -sfn "$VERSION_DIR" "$CURRENT_LINK"
fi fi
EXT_PATH="$CURRENT_LINK" # stable path used by wrappers EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
# Detect browsers # Detect browsers
declare -A BROWSERS declare -A BROWSERS
BROWSERS=( BROWSERS=(
[chromium]="Chromium" [chromium]="Chromium"
[google-chrome-stable]="Google Chrome" [google - chrome - stable]="Google Chrome"
[google-chrome]="Google Chrome" [google - chrome]="Google Chrome"
[brave-browser]="Brave" [brave - browser]="Brave"
[vivaldi-stable]="Vivaldi" [vivaldi - stable]="Vivaldi"
[vivaldi]="Vivaldi" [vivaldi]="Vivaldi"
[opera]="Opera" [opera]="Opera"
[thorium-browser]="Thorium" [thorium - browser]="Thorium"
) )
declare -A FIREFOXES declare -A FIREFOXES
FIREFOXES=( FIREFOXES=(
[firefox]="Firefox" [firefox]="Firefox"
[firefox-developer-edition]="Firefox Developer Edition" [firefox - developer - edition]="Firefox Developer Edition"
[librewolf]="LibreWolf" [librewolf]="LibreWolf"
) )
@ -173,15 +193,17 @@ user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
mkdir -p "$user_apps_dir" mkdir -p "$user_apps_dir"
create_wrapper_and_desktop() { create_wrapper_and_desktop() {
local bin="$1"; shift local bin="$1"
local pretty="$1"; shift shift
local pretty="$1"
shift
local wrapper="$wrap_bin_dir/${bin}-with-leechblock" local wrapper="$wrap_bin_dir/${bin}-with-leechblock"
local real_bin local real_bin
real_bin=$(command -v "$bin" || true) real_bin=$(command -v "$bin" || true)
[[ -z "$real_bin" ]] && return [[ -z $real_bin ]] && return
cat >"$wrapper" <<WRAP cat > "$wrapper" << WRAP
#!/usr/bin/env bash #!/usr/bin/env bash
exec "$real_bin" --load-extension="$EXT_PATH" "$@" exec "$real_bin" --load-extension="$EXT_PATH" "$@"
WRAP WRAP
@ -189,18 +211,18 @@ WRAP
# Try to reuse icon from an existing desktop file if available # Try to reuse icon from an existing desktop file if available
local sys_desktop existing_icon existing_name categories local sys_desktop existing_icon existing_name categories
sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2>/dev/null | head -n1 || true) sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2> /dev/null | head -n1 || true)
if [[ -n "$sys_desktop" ]]; then if [[ -n $sys_desktop ]]; then
existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true) existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true)
existing_name=$(awk -F= '/^Name=/{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) categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true)
fi fi
[[ -z "$existing_icon" ]] && existing_icon="$bin" [[ -z $existing_icon ]] && existing_icon="$bin"
[[ -z "$existing_name" ]] && existing_name="$pretty" [[ -z $existing_name ]] && existing_name="$pretty"
[[ -z "$categories" ]] && categories="Network;WebBrowser;" [[ -z $categories ]] && categories="Network;WebBrowser;"
local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop" local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop"
cat >"$desktop_file" <<DESK cat > "$desktop_file" << DESK
[Desktop Entry] [Desktop Entry]
Name=${existing_name} (LeechBlock) Name=${existing_name} (LeechBlock)
Exec=${wrapper} %U Exec=${wrapper} %U
@ -218,14 +240,14 @@ DESK
info "Detecting installed browsers…" info "Detecting installed browsers…"
for bin in "${!BROWSERS[@]}"; do 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]}" create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}"
fi fi
done done
ff_found=0 ff_found=0
for bin in "${!FIREFOXES[@]}"; do 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 ff_found=1
fi fi
done done
@ -239,7 +261,7 @@ fi
if [[ $ff_found -eq 1 ]]; then if [[ $ff_found -eq 1 ]]; then
echo echo
warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing." 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: Options:
1) Install from Mozilla Add-ons (recommended): 1) Install from Mozilla Add-ons (recommended):
https://addons.mozilla.org/firefox/addon/leechblock-ng/ 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 # Determine policy directories for detected Firefox-like browsers
declare -a POLICY_DIRS declare -a POLICY_DIRS
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") POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution")
fi 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") POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution")
fi 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") POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution")
fi fi
# Generic mozilla path as fallback # 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 if sudo test -f "$existing"; then
info "Merging into existing policies.json at $existing" info "Merging into existing policies.json at $existing"
sudo cp "$existing" "$tmp_pol" 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" ' merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" '
.policies |= (. // {}) | .policies |= (. // {}) |
.policies.ExtensionSettings |= (. // {}) | .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].installation_mode = "force_installed" |
.policies.ExtensionSettings[$id].install_url = $url .policies.ExtensionSettings[$id].install_url = $url
' "$tmp_pol") || merged="" ' "$tmp_pol") || merged=""
if [[ -n "$merged" ]]; then if [[ -n $merged ]]; then
printf '%s\n' "$merged" > "$tmp_pol" printf '%s\n' "$merged" > "$tmp_pol"
else else
warn "jq merge failed; skipping $pol_target" warn "jq merge failed; skipping $pol_target"
rm -f "$tmp_pol"; continue rm -f "$tmp_pol"
continue
fi fi
else else
warn "jq not available; creating minimal policies.json (existing file will be backed up)." warn "jq not available; creating minimal policies.json (existing file will be backed up)."
sudo cp "$existing" "${existing}.bak.$(date +%s)" sudo cp "$existing" "${existing}.bak.$(date +%s)"
cat > "$tmp_pol" <<JSON cat > "$tmp_pol" << JSON
{ {
"policies": { "policies": {
"ExtensionSettings": { "ExtensionSettings": {
@ -325,7 +348,7 @@ JSON
fi fi
else else
info "Creating new policies.json at $pol_target" info "Creating new policies.json at $pol_target"
cat > "$tmp_pol" <<JSON cat > "$tmp_pol" << JSON
{ {
"policies": { "policies": {
"ExtensionSettings": { "ExtensionSettings": {

View File

@ -14,7 +14,6 @@ GREEN='\033[0;32m'
YELLOW='\033[0;33m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# Script locations # Script locations
@ -57,7 +56,7 @@ else
echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}" echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}"
fi fi
chmod +x "$WRAPPER_DEST" 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 # Automatically use symbolic link installation method
echo -e "${YELLOW}Installing using symbolic link method...${NC}" echo -e "${YELLOW}Installing using symbolic link method...${NC}"

File diff suppressed because it is too large Load Diff

View File

@ -24,254 +24,262 @@ BELL="🔔"
# Function to draw a box around text # Function to draw a box around text
draw_box() { draw_box() {
local text="$1" local text="$1"
local width=${#text} local width=${#text}
local padding=2 local padding=2
local total_width=$((width + padding * 2)) local total_width=$((width + padding * 2))
# Top border # Top border
printf "┌" printf "┌"
printf "─%.0s" $(seq 1 $total_width) printf "─%.0s" $(seq 1 $total_width)
printf "┐\n" printf "┐\n"
# Content with padding # Content with padding
printf "│%*s%s%*s│\n" $padding "" "$text" $padding "" printf "│%*s%s%*s│\n" $padding "" "$text" $padding ""
# Bottom border # Bottom border
printf "└" printf "└"
printf "─%.0s" $(seq 1 $total_width) printf "─%.0s" $(seq 1 $total_width)
printf "┘\n" printf "┘\n"
} }
# Function to show current day status # Function to show current day status
show_day_status() { show_day_status() {
local day_of_week=$(date +%u) local day_of_week
local day_name=$(date +%A) day_of_week=$(date +%u)
local today=$(date +%Y-%m-%d)
printf "${BLUE}${CALENDAR} Day Status${NC}\n" printf '%s%s Day Status%s\n' "$BLUE" "$CALENDAR" "$NC"
printf "═══════════════\n" printf '═══════════════\n'
# Show all days with status # Show all days with status
local days=("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday") local days=("Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday")
local monitored=(1 0 0 0 1 1 1) # 1=monitored, 0=not monitored local monitored=(1 0 0 0 1 1 1) # 1=monitored, 0=not monitored
for i in {0..6}; do for i in {0..6}; do
local day_num=$((i + 1)) local day_num=$((i + 1))
if [[ $day_num -eq 7 ]]; then day_num=0; fi # Sunday is 0 in some contexts if [[ $day_num -eq 7 ]]; then day_num=0; fi # Sunday is 0 in some contexts
if [[ ${monitored[$i]} -eq 1 ]]; then if [[ ${monitored[$i]} -eq 1 ]]; then
if [[ $day_of_week -eq $((i + 1)) ]] || [[ $day_of_week -eq 7 && $i -eq 6 ]]; 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 else
printf "${CYAN}${CHECK} ${days[$i]} (monitored)${NC}\n" printf '%s%s %s (monitored)%s\n' "$CYAN" "$CHECK" "${days[$i]}" "$NC"
fi fi
else else
if [[ $day_of_week -eq $((i + 1)) ]]; then 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 else
printf "${GRAY}${days[$i]}${NC}\n" printf '%s○ %s%s\n' "$GRAY" "${days[$i]}" "$NC"
fi fi
fi fi
done done
printf "\n" printf "\n"
} }
# Function to show time window status # Function to show time window status
show_time_status() { show_time_status() {
local current_hour=$(date +%H) local current_hour current_minute current_hour_num
local current_minute=$(date +%M) current_hour=$(date +%H)
local current_hour_num=$((10#$current_hour)) current_minute=$(date +%M)
current_hour_num=$((10#$current_hour))
printf "${YELLOW}${CLOCK} Time Window Status${NC}\n" printf '%s%s Time Window Status%s\n' "$YELLOW" "$CLOCK" "$NC"
printf "═══════════════════════\n" printf '═══════════════════════\n'
# Show 24-hour timeline with window highlighted # Show 24-hour timeline with window highlighted
printf "Timeline (24-hour format):\n" printf 'Timeline (24-hour format):\n'
printf "00 01 02 03 04 " printf '00 01 02 03 04 '
printf "${GREEN}05 06 07${NC} " 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 '08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n'
printf " " printf ' '
printf "${GREEN}▲─────▲${NC}\n" printf '%s▲─────▲%s\n' "$GREEN" "$NC"
printf " " printf ' '
printf "${GREEN}Expected Window${NC}\n" printf '%sExpected Window%s\n' "$GREEN" "$NC"
# Current time indicator # 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 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 else
printf "Status: ${YELLOW}○ Outside expected window${NC}\n" printf 'Status: %s○ Outside expected window%s\n' "$YELLOW" "$NC"
fi fi
printf "\n" printf '\n'
} }
# Function to show boot time analysis # Function to show boot time analysis
show_boot_analysis() { show_boot_analysis() {
printf "${PURPLE}${COMPUTER} Boot Time Analysis${NC}\n" printf '%s%s Boot Time Analysis%s\n' "$PURPLE" "$COMPUTER" "$NC"
printf "═══════════════════════\n" printf '═══════════════════════\n'
# Get boot time # Get boot time
local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") local uptime_seconds boot_time boot_date boot_time_only boot_hour boot_hour_num today
local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
local boot_date=$(echo "$boot_time" | cut -d' ' -f1) boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
local boot_time_only=$(echo "$boot_time" | cut -d' ' -f2) boot_date=$(echo "$boot_time" | cut -d' ' -f1)
local boot_hour=$(echo "$boot_time_only" | cut -d':' -f1) boot_time_only=$(echo "$boot_time" | cut -d' ' -f2)
local boot_hour_num=$((10#$boot_hour)) boot_hour=$(echo "$boot_time_only" | cut -d':' -f1)
local today=$(date +%Y-%m-%d) 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 if [[ $boot_date == "$today" ]]; then
printf "Boot date: ${GREEN}${CHECK} Today${NC}\n" printf 'Boot date: %s%s Today%s\n' "$GREEN" "$CHECK" "$NC"
if [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then 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 'Boot window: %s%s Within expected window (5AM-8AM)%s\n' "$GREEN" "$CHECK" "$NC"
printf "Status: ${GREEN}${CHECK} COMPLIANT${NC}\n" 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"
fi
else else
printf "Boot date: ${YELLOW}○ Not today ($boot_date)${NC}\n" printf 'Boot window: %s%s Outside expected window%s\n' "$RED" "$CROSS" "$NC"
printf "Status: ${YELLOW}○ System was not booted today${NC}\n" printf 'Status: %s%s NON-COMPLIANT%s\n' "$RED" "$WARNING" "$NC"
fi fi
else
printf 'Boot date: %s○ Not today (%s)%s\n' "$YELLOW" "$boot_date" "$NC"
printf 'Status: %s○ System was not booted today%s\n' "$YELLOW" "$NC"
fi
printf "\n" printf '\n'
} }
# Function to show monitoring system status # Function to show monitoring system status
show_system_status() { show_system_status() {
printf "${CYAN}${BELL} Monitoring System${NC}\n" printf '%s%s Monitoring System%s\n' "$CYAN" "$BELL" "$NC"
printf "═══════════════════════\n" printf '═══════════════════════\n'
# Check if timer exists and is enabled # Check if timer exists and is enabled
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then
printf "Service: ${GREEN}${CHECK} ENABLED${NC}\n" 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"
else
printf "Timer: ${RED}${CROSS} INACTIVE${NC}\n"
fi
# Show next check time
local next_check=$(systemctl list-timers pc-startup-monitor.timer --no-pager 2>/dev/null | grep pc-startup-monitor | awk '{print $1, $2, $3}' || echo "Not scheduled")
printf "Next check: ${WHITE}$next_check${NC}\n"
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
printf 'Timer: %s%s ACTIVE%s\n' "$GREEN" "$CHECK" "$NC"
else else
printf "Service: ${RED}${CROSS} NOT ENABLED${NC}\n" printf 'Timer: %s%s INACTIVE%s\n' "$RED" "$CROSS" "$NC"
printf "Timer: ${RED}${CROSS} NOT ACTIVE${NC}\n"
fi fi
printf "\n" # Show next check time
local next_check
next_check=$(systemctl list-timers pc-startup-monitor.timer --no-pager 2> /dev/null | grep pc-startup-monitor | awk '{print $1, $2, $3}' || echo "Not scheduled")
printf 'Next check: %s%s%s\n' "$WHITE" "$next_check" "$NC"
else
printf 'Service: %s%s NOT ENABLED%s\n' "$RED" "$CROSS" "$NC"
printf 'Timer: %s%s NOT ACTIVE%s\n' "$RED" "$CROSS" "$NC"
fi
printf '\n'
} }
# Function to show overall compliance status # Function to show overall compliance status
show_compliance_overview() { show_compliance_overview() {
local day_of_week=$(date +%u) local day_of_week current_hour current_hour_num
local current_hour=$(date +%H) day_of_week=$(date +%u)
local current_hour_num=$((10#$current_hour)) current_hour=$(date +%H)
current_hour_num=$((10#$current_hour))
# Check if today is monitored # Check if today is monitored
local is_monitored=false 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 is_monitored=true
fi fi
printf "${WHITE}" printf '%s' "$WHITE"
draw_box "COMPLIANCE OVERVIEW" draw_box "COMPLIANCE OVERVIEW"
printf "${NC}\n" printf '%s\n' "$NC"
if [[ "$is_monitored" == true ]]; then if [[ $is_monitored == true ]]; then
printf "Today: ${GREEN}${CHECK} Monitored day${NC}\n" printf 'Today: %s%s Monitored day%s\n' "$GREEN" "$CHECK" "$NC"
# Check current compliance # Check current compliance
if [[ $current_hour_num -ge 5 && $current_hour_num -lt 8 ]]; then 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 'Current status: %s%s PC is on during expected window%s\n' "$GREEN" "$CHECK" "$NC"
printf "Action needed: ${GREEN}None - currently compliant${NC}\n" 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)
if [[ "$boot_date" == "$today" ]] && [[ $boot_hour_num -ge 5 && $boot_hour_num -lt 8 ]]; then
printf "Current status: ${GREEN}${CHECK} PC was booted in expected window${NC}\n"
printf "Action needed: ${GREEN}None - compliant${NC}\n"
else
printf "Current status: ${RED}${WARNING} PC was NOT booted in expected window${NC}\n"
printf "Action needed: ${YELLOW}Warning will be shown at 8:30 AM${NC}\n"
fi
fi
else else
printf "Today: ${GRAY}○ Not a monitored day${NC}\n" # Check if booted in window
printf "Current status: ${GRAY}No monitoring required${NC}\n" local uptime_seconds boot_time boot_date boot_hour boot_hour_num today
printf "Action needed: ${GRAY}None${NC}\n" uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
fi 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)
printf "\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: %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: %s○ Not a monitored day%s\n' "$GRAY" "$NC"
printf 'Current status: %sNo monitoring required%s\n' "$GRAY" "$NC"
printf 'Action needed: %sNone%s\n' "$GRAY" "$NC"
fi
printf '\n'
} }
# Function to show recent activity # Function to show recent activity
show_recent_activity() { show_recent_activity() {
printf "${GRAY}📋 Recent Activity${NC}\n" printf '%s📋 Recent Activity%s\n' "$GRAY" "$NC"
printf "════════════════\n" printf '════════════════\n'
# Show last 5 log entries # 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 if [[ $logs == "No logs found" ]]; then
printf "${GRAY}No recent monitoring activity${NC}\n" printf '%sNo recent monitoring activity%s\n' "$GRAY" "$NC"
else else
echo "$logs" | while IFS= read -r line; do echo "$logs" | while IFS= read -r line; do
if [[ $line == *"WARNING"* ]]; then if [[ $line == *"WARNING"* ]]; then
printf "${RED}$line${NC}\n" printf '%s%s%s\n' "$RED" "$line" "$NC"
elif [[ $line == *"compliance OK"* ]]; then elif [[ $line == *"compliance OK"* ]]; then
printf "${GREEN}$line${NC}\n" printf '%s%s%s\n' "$GREEN" "$line" "$NC"
else else
printf "${GRAY}$line${NC}\n" printf '%s%s%s\n' "$GRAY" "$line" "$NC"
fi fi
done done
fi fi
printf "\n" printf '\n'
} }
# Main display function # Main display function
main() { main() {
clear clear
# Header # Header
printf "${BLUE}" printf '%s' "$BLUE"
draw_box "PC STARTUP MONITOR - VISUAL STATUS" draw_box "PC STARTUP MONITOR - VISUAL STATUS"
printf "${NC}\n\n" printf '%s\n\n' "$NC"
printf "${WHITE}Current Date/Time: $(date)${NC}\n" local current_datetime system_uptime
printf "${WHITE}System Uptime: $(uptime -p)${NC}\n\n" 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 all status sections
show_day_status show_day_status
show_time_status show_time_status
show_boot_analysis show_boot_analysis
show_system_status show_system_status
show_compliance_overview show_compliance_overview
show_recent_activity show_recent_activity
# Footer with commands # Footer with commands
printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n" printf '%s═══════════════════════════════════════════════════════════════%s\n' "$BLUE" "$NC"
printf "${WHITE}Commands:${NC}\n" printf '%sCommands:%s\n' "$WHITE" "$NC"
printf " ${CYAN}sudo pc-startup-monitor-manager.sh status${NC} - Show system status\n" printf ' %s%s%s - Show system status\n' "$CYAN" "sudo pc-startup-monitor-manager.sh status" "$NC"
printf " ${CYAN}sudo pc-startup-monitor-manager.sh test${NC} - Test monitor now\n" printf ' %s%s%s - Test monitor now\n' "$CYAN" "sudo pc-startup-monitor-manager.sh test" "$NC"
printf " ${CYAN}sudo pc-startup-monitor-manager.sh logs${NC} - View detailed logs\n" printf ' %s%s%s - View detailed logs\n' "$CYAN" "sudo pc-startup-monitor-manager.sh logs" "$NC"
printf " ${CYAN}$0${NC} - Show this visual status\n" printf ' %s%s%s - Show this visual status\n' "$CYAN" "$0" "$NC"
printf "${BLUE}═══════════════════════════════════════════════════════════════${NC}\n" printf '%s═══════════════════════════════════════════════════════════════%s\n' "$BLUE" "$NC"
} }
# Run main function # Run main function

View File

@ -12,10 +12,10 @@ SCRIPT_NAME=$(basename "$0")
UNDO=false UNDO=false
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--undo) UNDO=true ;; --undo) UNDO=true ;;
-h|--help) -h | --help)
cat <<EOF cat << EOF
Usage: $SCRIPT_NAME [--undo] Usage: $SCRIPT_NAME [--undo]
Actions: Actions:
@ -29,40 +29,40 @@ Notes:
- Requires root privileges to write to /etc/* policy paths. Will self-elevate via sudo. - Requires root privileges to write to /etc/* policy paths. Will self-elevate via sudo.
- Restart affected browsers to apply changes. - Restart affected browsers to apply changes.
EOF EOF
exit 0 exit 0
;; ;;
esac esac
done done
# Re-exec as root if needed # Re-exec as root if needed
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "[info] Elevating privileges with sudo..." echo "[info] Elevating privileges with sudo..."
exec sudo -E bash "$0" "$@" exec sudo -E bash "$0" "$@"
fi fi
# Map binaries to a logical product key # Map binaries to a logical product key
declare -A BIN_TO_KEY=( declare -A BIN_TO_KEY=(
[thorium-browser]=thorium-browser [thorium - browser]=thorium-browser
[thorium]=thorium-browser [thorium]=thorium-browser
[chromium]=chromium [chromium]=chromium
[google-chrome]=google-chrome [google - chrome]=google-chrome
[google-chrome-stable]=google-chrome [google - chrome - stable]=google-chrome
[brave-browser]=brave-browser [brave - browser]=brave-browser
[vivaldi]=vivaldi [vivaldi]=vivaldi
[vivaldi-stable]=vivaldi [vivaldi - stable]=vivaldi
[microsoft-edge-stable]=microsoft-edge-stable [microsoft - edge - stable]=microsoft-edge-stable
[opera]=opera [opera]=opera
) )
# Candidate policy directories per product key (first existing or first creatable is used) # Candidate policy directories per product key (first existing or first creatable is used)
declare -A CANDIDATE_DIRS=( 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" [chromium]="/etc/chromium/policies/managed"
[google-chrome]="/etc/opt/chrome/policies/managed" [google - chrome]="/etc/opt/chrome/policies/managed"
[brave-browser]="/etc/opt/brave/policies/managed" [brave - browser]="/etc/opt/brave/policies/managed"
[vivaldi]="/etc/opt/vivaldi/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" [opera]="/etc/opt/opera/policies/managed"
) )
POLICY_FILENAME="99-disable-guest-mode.json" POLICY_FILENAME="99-disable-guest-mode.json"
@ -75,88 +75,89 @@ POLICY_JSON='{
# Discover installed browsers # Discover installed browsers
declare -A INSTALLED_KEYS=() declare -A INSTALLED_KEYS=()
for bin in "${!BIN_TO_KEY[@]}"; do 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]} key=${BIN_TO_KEY[$bin]}
INSTALLED_KEYS[$key]=1 INSTALLED_KEYS[$key]=1
fi fi
done done
if [[ ${#INSTALLED_KEYS[@]} -eq 0 ]]; then if [[ ${#INSTALLED_KEYS[@]} -eq 0 ]]; then
echo "[warn] No supported Chromium-based browsers detected in PATH. Proceeding to configure Thorium paths anyway." 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 fi
choose_target_dir() { choose_target_dir() {
local key="$1" local key="$1"
local IFS=":" local IFS=":"
local dirs local dirs
read -r -a dirs <<< "${CANDIDATE_DIRS[$key]:-}" read -r -a dirs <<< "${CANDIDATE_DIRS[$key]:-}"
# Prefer an existing directory; else pick the first candidate # Prefer an existing directory; else pick the first candidate
for d in "${dirs[@]}"; do for d in "${dirs[@]}"; do
if [[ -d "$d" ]]; then if [[ -d $d ]]; then
echo "$d" echo "$d"
return 0 return 0
fi fi
done done
echo "${dirs[0]}" echo "${dirs[0]}"
} }
apply_policy() { apply_policy() {
local target_dir="$1"; shift local target_dir="$1"
local file="$target_dir/$POLICY_FILENAME" shift
local file="$target_dir/$POLICY_FILENAME"
echo "[apply] $file" echo "[apply] $file"
mkdir -p "$target_dir" mkdir -p "$target_dir"
# Write atomically # Write atomically
local tmp local tmp
tmp=$(mktemp) tmp=$(mktemp)
printf '%s printf '%s
' "$POLICY_JSON" >"$tmp" ' "$POLICY_JSON" > "$tmp"
install -m 0644 "$tmp" "$file" install -m 0644 "$tmp" "$file"
rm -f "$tmp" rm -f "$tmp"
} }
remove_policy() { remove_policy() {
local target_dir="$1"; shift local target_dir="$1"
local file="$target_dir/$POLICY_FILENAME" shift
local file="$target_dir/$POLICY_FILENAME"
if [[ -f "$file" ]]; then if [[ -f $file ]]; then
echo "[remove] $file" echo "[remove] $file"
rm -f -- "$file" rm -f -- "$file"
else else
echo "[skip] $file (not present)" echo "[skip] $file (not present)"
fi fi
} }
changed_any=false changed_any=false
for key in "${!INSTALLED_KEYS[@]}"; do for key in "${!INSTALLED_KEYS[@]}"; do
# If we somehow lack candidate dirs for a key, skip gracefully # 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." echo "[warn] No known policy directories for '$key'; skipping."
continue continue
fi fi
target_dir=$(choose_target_dir "$key") target_dir=$(choose_target_dir "$key")
if [[ "$UNDO" == true ]]; then if [[ $UNDO == true ]]; then
remove_policy "$target_dir" remove_policy "$target_dir"
else else
apply_policy "$target_dir" apply_policy "$target_dir"
fi fi
changed_any=true changed_any=true
done done
if [[ "$changed_any" == false ]]; then if [[ $changed_any == false ]]; then
echo "[info] Nothing to do." echo "[info] Nothing to do."
fi 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." echo "[done] Guest mode policy files removed where present. You may need to restart the browsers."
else else
echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers." echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers."
echo " If the Guest option still appears, it should be disabled/greyed out." echo " If the Guest option still appears, it should be disabled/greyed out."
fi fi

View File

@ -4,194 +4,190 @@
# Thursday-Sunday: Shutdown between 22:00-05:00 # Thursday-Sunday: Shutdown between 22:00-05:00
# Handles sudo privileges automatically # Handles sudo privileges automatically
set -e # Exit on any error set -e # Exit on any error
# Function to show usage # Function to show usage
show_usage() { show_usage() {
echo "Day-Specific Auto-Shutdown Setup for Arch Linux" echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
echo "===============================================" echo "==============================================="
echo "Usage: $0 [enable|disable|status]" echo "Usage: $0 [enable|disable|status]"
echo "" echo ""
echo "Commands:" echo "Commands:"
echo " enable - Set up automatic shutdown with day-specific windows (default)" echo " enable - Set up automatic shutdown with day-specific windows (default)"
echo " disable - Remove automatic shutdown" echo " disable - Remove automatic shutdown"
echo " status - Show current status" echo " status - Show current status"
echo "" echo ""
echo "Shutdown Schedule:" echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00" echo " Monday-Wednesday: 21:00-05:00"
echo " Thursday-Sunday: 22:00-05:00" echo " Thursday-Sunday: 22:00-05:00"
echo "" echo ""
} }
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to manage systemd services." echo "This script requires sudo privileges to manage systemd services."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Get the actual user (even when running with sudo) # Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER" ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER" USER_HOME="/home/$SUDO_USER"
else else
ACTUAL_USER="$USER" ACTUAL_USER="$USER"
USER_HOME="$HOME" USER_HOME="$HOME"
fi fi
# Function to disable and remove midnight shutdown # Function to disable and remove midnight shutdown
disable_midnight_shutdown() { disable_midnight_shutdown() {
echo "Disabling Day-Specific Auto-Shutdown" echo "Disabling Day-Specific Auto-Shutdown"
echo "====================================" echo "===================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $ACTUAL_USER" echo "User: $ACTUAL_USER"
echo ""
local timer_file="/etc/systemd/system/day-specific-shutdown.timer"
local service_file="/etc/systemd/system/day-specific-shutdown.service"
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
local removed_files=()
# Stop and disable timer if it exists
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "Stopping day-specific-shutdown timer..."
systemctl stop day-specific-shutdown.timer
echo "✓ Timer stopped"
fi
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
echo "Disabling day-specific-shutdown timer..."
systemctl disable day-specific-shutdown.timer
echo "✓ Timer disabled"
fi
# Remove timer file
if [[ -f $timer_file ]]; then
rm -f "$timer_file"
removed_files+=("$timer_file")
echo "✓ Removed timer file: $timer_file"
fi
# Remove service file
if [[ -f $service_file ]]; then
rm -f "$service_file"
removed_files+=("$service_file")
echo "✓ Removed service file: $service_file"
fi
# Remove management script
if [[ -f $script_file ]]; then
rm -f "$script_file"
removed_files+=("$script_file")
echo "✓ Removed management script: $script_file"
fi
# Remove check script
if [[ -f $check_script ]]; then
rm -f "$check_script"
removed_files+=("$check_script")
echo "✓ Removed check script: $check_script"
fi
# Reload systemd daemon
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Reloading systemd daemon..."
systemctl daemon-reload
echo "✓ Systemd daemon reloaded"
fi
echo ""
echo "============================================="
echo "Day-Specific Auto-Shutdown Removal Complete"
echo "============================================="
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Removed files:"
printf " %s\n" "${removed_files[@]}"
echo "" echo ""
echo "✓ Automatic day-specific shutdown has been completely disabled"
echo "✓ Your PC will no longer shutdown automatically"
else
echo "No day-specific shutdown configuration was found to remove."
fi
local timer_file="/etc/systemd/system/day-specific-shutdown.timer" echo ""
local service_file="/etc/systemd/system/day-specific-shutdown.service"
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
local removed_files=()
# Stop and disable timer if it exists
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
echo "Stopping day-specific-shutdown timer..."
systemctl stop day-specific-shutdown.timer
echo "✓ Timer stopped"
fi
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
echo "Disabling day-specific-shutdown timer..."
systemctl disable day-specific-shutdown.timer
echo "✓ Timer disabled"
fi
# Remove timer file
if [[ -f "$timer_file" ]]; then
rm -f "$timer_file"
removed_files+=("$timer_file")
echo "✓ Removed timer file: $timer_file"
fi
# Remove service file
if [[ -f "$service_file" ]]; then
rm -f "$service_file"
removed_files+=("$service_file")
echo "✓ Removed service file: $service_file"
fi
# Remove management script
if [[ -f "$script_file" ]]; then
rm -f "$script_file"
removed_files+=("$script_file")
echo "✓ Removed management script: $script_file"
fi
# Remove check script
if [[ -f "$check_script" ]]; then
rm -f "$check_script"
removed_files+=("$check_script")
echo "✓ Removed check script: $check_script"
fi
# Reload systemd daemon
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Reloading systemd daemon..."
systemctl daemon-reload
echo "✓ Systemd daemon reloaded"
fi
echo ""
echo "============================================="
echo "Day-Specific Auto-Shutdown Removal Complete"
echo "============================================="
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Removed files:"
printf " %s\n" "${removed_files[@]}"
echo ""
echo "✓ Automatic day-specific shutdown has been completely disabled"
echo "✓ Your PC will no longer shutdown automatically"
else
echo "No day-specific shutdown configuration was found to remove."
fi
echo ""
} }
# Function to show current status # Function to show current status
show_current_status() { show_current_status() {
echo "Day-Specific Auto-Shutdown Status" echo "Day-Specific Auto-Shutdown Status"
echo "=================================" echo "================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $ACTUAL_USER" echo "User: $ACTUAL_USER"
echo "" echo ""
local timer_exists=false local timer_exists=false
local service_exists=false
local script_exists=false
# Check if files exist # Check if files exist
if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then
timer_exists=true timer_exists=true
echo "✓ Timer file exists" echo "✓ Timer file exists"
else
echo "✗ Timer file missing"
fi
if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then
echo "✓ Service file exists"
else
echo "✗ Service file missing"
fi
if [[ -f "/usr/local/bin/day-specific-shutdown-manager.sh" ]]; then
echo "✓ Management script exists"
else
echo "✗ Management script missing"
fi
echo ""
# Check systemd status
if $timer_exists; then
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is enabled"
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is active"
echo ""
echo "Next scheduled shutdown check:"
systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | grep day-specific-shutdown || echo "Timer information not available"
else
echo "✗ Timer is not active"
fi
else else
echo "✗ Timer file missing" echo "✗ Timer is not enabled"
fi fi
else
echo "Status: NOT CONFIGURED"
fi
if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then echo ""
service_exists=true echo "Shutdown Schedule:"
echo "✓ Service file exists" echo " Monday-Wednesday: 21:00-05:00"
else echo " Thursday-Sunday: 22:00-05:00"
echo "✗ Service file missing" echo ""
fi
if [[ -f "/usr/local/bin/day-specific-shutdown-manager.sh" ]]; then
script_exists=true
echo "✓ Management script exists"
else
echo "✗ Management script missing"
fi
echo ""
# Check systemd status
if $timer_exists; then
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is enabled"
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is active"
echo ""
echo "Next scheduled shutdown check:"
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | grep day-specific-shutdown || echo "Timer information not available"
else
echo "✗ Timer is not active"
fi
else
echo "✗ Timer is not enabled"
fi
else
echo "Status: NOT CONFIGURED"
fi
echo ""
echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00"
echo " Thursday-Sunday: 22:00-05:00"
echo ""
} }
# Function to create the shutdown service # Function to create the shutdown service
create_shutdown_service() { create_shutdown_service() {
echo "" echo ""
echo "1. Creating Systemd Shutdown Service..." echo "1. Creating Systemd Shutdown Service..."
echo "======================================" echo "======================================"
local service_file="/etc/systemd/system/day-specific-shutdown.service" local service_file="/etc/systemd/system/day-specific-shutdown.service"
cat > "$service_file" << 'EOF' cat > "$service_file" << 'EOF'
[Unit] [Unit]
Description=Automatic PC shutdown with day-specific time windows Description=Automatic PC shutdown with day-specific time windows
DefaultDependencies=false DefaultDependencies=false
@ -205,18 +201,18 @@ StandardOutput=journal
StandardError=journal StandardError=journal
EOF EOF
echo "✓ Created systemd service: $service_file" echo "✓ Created systemd service: $service_file"
} }
# Function to create the shutdown timer # Function to create the shutdown timer
create_shutdown_timer() { create_shutdown_timer() {
echo "" echo ""
echo "2. Creating Systemd Shutdown Timer..." echo "2. Creating Systemd Shutdown Timer..."
echo "===================================" echo "==================================="
local timer_file="/etc/systemd/system/day-specific-shutdown.timer" local timer_file="/etc/systemd/system/day-specific-shutdown.timer"
cat > "$timer_file" << 'EOF' cat > "$timer_file" << 'EOF'
[Unit] [Unit]
Description=Timer for automatic PC shutdown with day-specific windows Description=Timer for automatic PC shutdown with day-specific windows
Requires=day-specific-shutdown.service Requires=day-specific-shutdown.service
@ -248,18 +244,18 @@ RandomizedDelaySec=0
WantedBy=timers.target WantedBy=timers.target
EOF EOF
echo "✓ Created systemd timer: $timer_file" echo "✓ Created systemd timer: $timer_file"
} }
# Function to create management script # Function to create management script
create_management_script() { create_management_script() {
echo "" echo ""
echo "3. Creating Management Script..." echo "3. Creating Management Script..."
echo "==============================" echo "=============================="
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
cat > "$script_file" << 'EOF' cat > "$script_file" << 'EOF'
#!/bin/bash #!/bin/bash
# Day-Specific Auto-Shutdown Manager # Day-Specific Auto-Shutdown Manager
# Provides easy management of the day-specific shutdown feature # Provides easy management of the day-specific shutdown feature
@ -322,19 +318,19 @@ case "$1" in
esac esac
EOF EOF
chmod +x "$script_file" chmod +x "$script_file"
echo "✓ Created management script: $script_file" echo "✓ Created management script: $script_file"
} }
# Function to create smart shutdown check script # Function to create smart shutdown check script
create_shutdown_check_script() { create_shutdown_check_script() {
echo "" echo ""
echo "4. Creating Smart Shutdown Check Script..." echo "4. Creating Smart Shutdown Check Script..."
echo "========================================" echo "========================================"
local check_script="/usr/local/bin/day-specific-shutdown-check.sh" local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
cat > "$check_script" << 'EOF' cat > "$check_script" << 'EOF'
#!/bin/bash #!/bin/bash
# Smart day-specific shutdown check script # Smart day-specific shutdown check script
# Different shutdown windows based on day of week: # Different shutdown windows based on day of week:
@ -401,206 +397,206 @@ else
fi fi
EOF EOF
chmod +x "$check_script" chmod +x "$check_script"
echo "✓ Created smart shutdown check script: $check_script" echo "✓ Created smart shutdown check script: $check_script"
} }
# Function to enable the timer # Function to enable the timer
enable_timer() { enable_timer() {
echo "" echo ""
echo "5. Enabling Shutdown Timer..." echo "5. Enabling Shutdown Timer..."
echo "============================" echo "============================"
# Reload systemd daemon # Reload systemd daemon
systemctl daemon-reload systemctl daemon-reload
echo "✓ Reloaded systemd daemon" echo "✓ Reloaded systemd daemon"
# Enable the timer # Enable the timer
systemctl enable day-specific-shutdown.timer systemctl enable day-specific-shutdown.timer
echo "✓ Enabled day-specific-shutdown timer" echo "✓ Enabled day-specific-shutdown timer"
# Start the timer # Start the timer
systemctl start day-specific-shutdown.timer systemctl start day-specific-shutdown.timer
echo "✓ Started day-specific-shutdown timer" echo "✓ Started day-specific-shutdown timer"
} }
# Function to test the setup # Function to test the setup
test_setup() { test_setup() {
echo "" echo ""
echo "6. Testing Setup..." echo "6. Testing Setup..."
echo "==================" echo "=================="
echo "Service files:" echo "Service files:"
if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then if [[ -f "/etc/systemd/system/day-specific-shutdown.service" ]]; then
echo "✓ Service file exists" echo "✓ Service file exists"
else else
echo "✗ Service file missing" echo "✗ Service file missing"
fi fi
if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then if [[ -f "/etc/systemd/system/day-specific-shutdown.timer" ]]; then
echo "✓ Timer file exists" echo "✓ Timer file exists"
else else
echo "✗ Timer file missing" echo "✗ Timer file missing"
fi fi
echo "" echo ""
echo "Timer status:" 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" echo "✓ Timer is enabled"
else else
echo "✗ Timer is not enabled" echo "✗ Timer is not enabled"
fi 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" echo "✓ Timer is active"
else else
echo "✗ Timer is not active" echo "✗ Timer is not active"
fi fi
echo "" echo ""
echo "Next scheduled checks:" 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 # Function to show final instructions
show_instructions() { show_instructions() {
echo "" echo ""
echo "=================================================" echo "================================================="
echo "Day-Specific Auto-Shutdown Setup Complete" echo "Day-Specific Auto-Shutdown Setup Complete"
echo "=================================================" echo "================================================="
echo "Summary:" echo "Summary:"
echo "✓ Systemd service created (/etc/systemd/system/day-specific-shutdown.service)" echo "✓ Systemd service created (/etc/systemd/system/day-specific-shutdown.service)"
echo "✓ Systemd timer created (/etc/systemd/system/day-specific-shutdown.timer)" echo "✓ Systemd timer created (/etc/systemd/system/day-specific-shutdown.timer)"
echo "✓ Management script created (/usr/local/bin/day-specific-shutdown-manager.sh)" echo "✓ Management script created (/usr/local/bin/day-specific-shutdown-manager.sh)"
echo "✓ Smart check script created (/usr/local/bin/day-specific-shutdown-check.sh)" echo "✓ Smart check script created (/usr/local/bin/day-specific-shutdown-check.sh)"
echo "✓ Timer enabled and started" echo "✓ Timer enabled and started"
echo "" echo ""
echo "Shutdown Schedule:" echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)"
echo "" echo ""
echo "Management commands:" echo "Management commands:"
echo " sudo day-specific-shutdown-manager.sh status - Check status" echo " sudo day-specific-shutdown-manager.sh status - Check status"
echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs" echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs"
echo "" echo ""
echo "How it works:" echo "How it works:"
echo "• Timer checks every 30 minutes during potential shutdown windows" echo "• Timer checks every 30 minutes during potential shutdown windows"
echo "• Smart logic determines shutdown eligibility based on day and time" echo "• Smart logic determines shutdown eligibility based on day and time"
echo "• Prevents accidental shutdowns outside designated time windows" echo "• Prevents accidental shutdowns outside designated time windows"
echo "" echo ""
echo "WARNING: This will automatically shutdown your PC during designated hours." echo "WARNING: This will automatically shutdown your PC during designated hours."
echo "Make sure to save your work before the shutdown windows!" echo "Make sure to save your work before the shutdown windows!"
echo "" echo ""
} }
# Function to prompt for confirmation # Function to prompt for confirmation
confirm_setup() { confirm_setup() {
echo "" echo ""
echo "WARNING: Day-Specific Auto-Shutdown Confirmation" echo "WARNING: Day-Specific Auto-Shutdown Confirmation"
echo "===============================================" echo "==============================================="
echo "This will set up your PC to automatically shutdown during specific time windows." echo "This will set up your PC to automatically shutdown during specific time windows."
echo "" echo ""
echo "Shutdown Schedule:" echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)"
echo "" echo ""
echo "Important considerations:" echo "Important considerations:"
echo "- Any unsaved work will be lost during shutdown windows" echo "- Any unsaved work will be lost during shutdown windows"
echo "- Running processes will be terminated" echo "- Running processes will be terminated"
echo "- Downloads/uploads in progress will be interrupted" echo "- Downloads/uploads in progress will be interrupted"
echo "- You'll need to manually power on your PC each day" echo "- You'll need to manually power on your PC each day"
echo "- Timer checks every 30 minutes during potential shutdown windows" echo "- Timer checks every 30 minutes during potential shutdown windows"
echo "" 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 case "$confirm" in
[yY]|[yY][eE][sS]) [yY] | [yY][eE][sS])
echo "Proceeding with setup..." echo "Proceeding with setup..."
return 0 return 0
;; ;;
*) *)
echo "Setup cancelled." echo "Setup cancelled."
exit 0 exit 0
;; ;;
esac esac
} }
# Function to confirm disable # Function to confirm disable
confirm_disable() { confirm_disable() {
echo "" echo ""
echo "Disable Day-Specific Auto-Shutdown Confirmation" echo "Disable Day-Specific Auto-Shutdown Confirmation"
echo "===============================================" echo "==============================================="
echo "This will completely remove the automatic day-specific shutdown configuration." echo "This will completely remove the automatic day-specific shutdown configuration."
echo "" echo ""
echo "After disabling:" echo "After disabling:"
echo "- Your PC will no longer shutdown automatically during any time windows" echo "- Your PC will no longer shutdown automatically during any time windows"
echo "- All related systemd services and timers will be removed" echo "- All related systemd services and timers will be removed"
echo "- The management and check scripts will be deleted" echo "- The management and check scripts will be deleted"
echo "" 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 case "$confirm" in
[yY]|[yY][eE][sS]) [yY] | [yY][eE][sS])
echo "Proceeding with disable..." echo "Proceeding with disable..."
return 0 return 0
;; ;;
*) *)
echo "Disable cancelled." echo "Disable cancelled."
exit 0 exit 0
;; ;;
esac esac
} }
# Main execution flow for enable # Main execution flow for enable
enable_midnight_shutdown() { enable_midnight_shutdown() {
echo "Day-Specific Auto-Shutdown Setup for Arch Linux" echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
echo "===============================================" echo "==============================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $ACTUAL_USER" echo "User: $ACTUAL_USER"
echo "Target user: $ACTUAL_USER" echo "Target user: $ACTUAL_USER"
echo "User home: $USER_HOME" echo "User home: $USER_HOME"
# Confirm setup # Confirm setup
confirm_setup confirm_setup
# Create systemd files # Create systemd files
create_shutdown_service create_shutdown_service
create_shutdown_timer create_shutdown_timer
create_management_script create_management_script
create_shutdown_check_script create_shutdown_check_script
# Enable and start timer # Enable and start timer
enable_timer enable_timer
# Test setup # Test setup
test_setup test_setup
# Show instructions # Show instructions
show_instructions show_instructions
} }
# Parse command line arguments # Parse command line arguments
case "${1:-enable}" in case "${1:-enable}" in
"enable") "enable")
check_sudo "$@" check_sudo "$@"
enable_midnight_shutdown enable_midnight_shutdown
;; ;;
"disable") "disable")
check_sudo "$@" check_sudo "$@"
confirm_disable confirm_disable
disable_midnight_shutdown disable_midnight_shutdown
;; ;;
"status") "status")
check_sudo "$@" check_sudo "$@"
show_current_status show_current_status
;; ;;
"help"|"-h"|"--help") "help" | "-h" | "--help")
show_usage show_usage
;; ;;
*) *)
echo "Error: Unknown command '$1'" echo "Error: Unknown command '$1'"
echo "" echo ""
show_usage show_usage
exit 1 exit 1
;; ;;
esac esac

View File

@ -3,59 +3,59 @@
# Checks if PC was turned on between 5AM-8AM on Monday, Friday, Saturday, Sunday # Checks if PC was turned on between 5AM-8AM on Monday, Friday, Saturday, Sunday
# Handles sudo privileges automatically # Handles sudo privileges automatically
set -e # Exit on any error set -e # Exit on any error
# Default to non-interactive mode # Default to non-interactive mode
INTERACTIVE_MODE=false INTERACTIVE_MODE=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--interactive) -i | --interactive)
INTERACTIVE_MODE=true INTERACTIVE_MODE=true
shift shift
;; ;;
-h|--help) -h | --help)
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
echo "Options:" echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)" echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
exit 0 exit 0
;; ;;
*) *)
echo "Unknown option: $1" echo "Unknown option: $1"
echo "Use -h or --help for usage information" echo "Use -h or --help for usage information"
exit 1 exit 1
;; ;;
esac esac
done done
echo "PC Startup Time Monitor for Arch Linux" echo "PC Startup Time Monitor for Arch Linux"
echo "======================================" echo "======================================"
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: ${SUDO_USER:-$USER}" echo "User: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)" echo "Mode: Interactive (prompts enabled)"
else else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)" echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
fi fi
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to access system logs and create services." echo "This script requires sudo privileges to access system logs and create services."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Get the actual user (even when running with sudo) # Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER" ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER" USER_HOME="/home/$SUDO_USER"
else else
ACTUAL_USER="$USER" ACTUAL_USER="$USER"
USER_HOME="$HOME" USER_HOME="$HOME"
fi fi
echo "Target user: $ACTUAL_USER" echo "Target user: $ACTUAL_USER"
@ -63,138 +63,147 @@ echo "User home: $USER_HOME"
# Function to check if today is a monitored day # Function to check if today is a monitored day
is_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) # 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 return 0 # Yes, it's a monitored day
else else
return 1 # No, it's not a monitored day return 1 # No, it's not a monitored day
fi fi
} }
# Function to check if current time is between 5AM and 8AM # Function to check if current time is between 5AM and 8AM
is_current_time_in_window() { is_current_time_in_window() {
local current_hour=$(date +%H) local current_hour current_hour_num
local current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues 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 if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
return 0 # Yes, current time is in the 5AM-8AM window return 0 # Yes, current time is in the 5AM-8AM window
else else
return 1 # No, current time is outside the window return 1 # No, current time is outside the window
fi fi
} }
# Function to check if PC was booted between 5AM-8AM today # Function to check if PC was booted between 5AM-8AM today
was_booted_in_window_today() { was_booted_in_window_today() {
local today=$(date +%Y-%m-%d) local today boot_time
local boot_time="" today=$(date +%Y-%m-%d)
boot_time=""
# Get the last boot time using multiple methods for reliability # 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 # Method 1: Calculate boot time from uptime
local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") local uptime_seconds
if [[ $uptime_seconds -gt 0 ]]; then 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") if [[ $uptime_seconds -gt 0 ]]; then
fi boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi fi
fi
# Method 2: Use systemd if available (fallback) # Method 2: Use systemd if available (fallback)
if [[ -z "$boot_time" ]] && command -v systemctl &>/dev/null; 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 "") boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2> /dev/null || echo "")
if [[ -n "$boot_time" ]]; then if [[ -n $boot_time ]]; then
# This gives us relative time, need to calculate absolute time # This gives us relative time, need to calculate absolute time
local current_time=$(date +%s) local current_time uptime_sec
local uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") current_time=$(date +%s)
boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S") uptime_sec=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
fi boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S")
fi fi
fi
# Method 3: Use who -b (fallback) # Method 3: Use who -b (fallback)
if [[ -z "$boot_time" ]] && command -v who &>/dev/null; then if [[ -z $boot_time ]] && command -v who &> /dev/null; then
boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "") boot_time=$(who -b | awk '{print $3, $4}' 2> /dev/null || echo "")
if [[ -n "$boot_time" ]]; then if [[ -n $boot_time ]]; then
boot_time="$today $boot_time" boot_time="$today $boot_time"
fi
fi fi
fi
# Method 4: Use /proc/uptime as final fallback # Method 4: Use /proc/uptime as final fallback
if [[ -z "$boot_time" ]]; then if [[ -z $boot_time ]]; then
local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") local uptime_seconds
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
fi boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi
echo "Boot time detected: $boot_time" echo "Boot time detected: $boot_time"
# Check if boot time is from today # Check if boot time is from today
local boot_date=$(echo "$boot_time" | cut -d' ' -f1) local boot_date
if [[ "$boot_date" != "$today" ]]; then boot_date=$(echo "$boot_time" | cut -d' ' -f1)
echo "PC was not booted today (boot date: $boot_date, today: $today)" if [[ $boot_date != "$today" ]]; then
return 1 # Not booted today echo "PC was not booted today (boot date: $boot_date, today: $today)"
fi return 1 # Not booted today
fi
# Extract hour from boot time # Extract hour from boot time
local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) local boot_hour boot_hour_num
local boot_hour_num=$((10#$boot_hour)) # Convert to decimal 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" echo "Boot hour: $boot_hour_num"
# Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM) # Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM)
if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then
echo "PC was booted in the expected window (5AM-8AM)" echo "PC was booted in the expected window (5AM-8AM)"
return 0 # Yes, booted in window return 0 # Yes, booted in window
else else
echo "PC was NOT booted in the expected window (5AM-8AM)" echo "PC was NOT booted in the expected window (5AM-8AM)"
return 1 # No, not booted in window return 1 # No, not booted in window
fi fi
} }
# Function to show notification/warning # Function to show notification/warning
show_startup_warning() { show_startup_warning() {
local day_name=$(date +%A) local day_name current_time today
local current_time=$(date +"%H:%M") day_name=$(date +%A)
local today=$(date +%Y-%m-%d) current_time=$(date +"%H:%M")
today=$(date +%Y-%m-%d)
echo "" echo ""
echo "⚠️ PC STARTUP TIME WARNING" echo "⚠️ PC STARTUP TIME WARNING"
echo "==========================" echo "=========================="
echo "Date: $today ($day_name)" echo "Date: $today ($day_name)"
echo "Current time: $current_time" echo "Current time: $current_time"
echo "" echo ""
echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today," echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today,"
echo "but it was not turned on during that time window." echo "but it was not turned on during that time window."
echo "" echo ""
echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM" echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM"
echo "Actual: PC was turned on outside the expected window" echo "Actual: PC was turned on outside the expected window"
echo "" echo ""
# Log the warning # Log the warning
logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today" 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 # 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 if [[ $EUID -eq 0 ]]; then
# Running as root, send notification as user # 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 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 fi
fi
echo "This warning has been logged to the system journal." echo "This warning has been logged to the system journal."
echo "You can view startup logs with: journalctl -t pc-startup-monitor" echo "You can view startup logs with: journalctl -t pc-startup-monitor"
echo "" echo ""
} }
# Function to create the monitoring service # Function to create the monitoring service
create_monitoring_service() { create_monitoring_service() {
echo "" echo ""
echo "1. Creating PC Startup Monitor Service..." echo "1. Creating PC Startup Monitor Service..."
echo "=======================================" echo "======================================="
local service_file="/etc/systemd/system/pc-startup-monitor.service" local service_file="/etc/systemd/system/pc-startup-monitor.service"
cat > "$service_file" << 'EOF' cat > "$service_file" << 'EOF'
[Unit] [Unit]
Description=PC Startup Time Monitor Description=PC Startup Time Monitor
After=multi-user.target After=multi-user.target
@ -211,18 +220,18 @@ RemainAfterExit=true
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
echo "✓ Created monitoring service: $service_file" echo "✓ Created monitoring service: $service_file"
} }
# Function to create the monitoring timer # Function to create the monitoring timer
create_monitoring_timer() { create_monitoring_timer() {
echo "" echo ""
echo "2. Creating PC Startup Monitor Timer..." echo "2. Creating PC Startup Monitor Timer..."
echo "=====================================" echo "====================================="
local timer_file="/etc/systemd/system/pc-startup-monitor.timer" local timer_file="/etc/systemd/system/pc-startup-monitor.timer"
cat > "$timer_file" << 'EOF' cat > "$timer_file" << 'EOF'
[Unit] [Unit]
Description=Timer for PC startup monitoring Description=Timer for PC startup monitoring
Requires=pc-startup-monitor.service Requires=pc-startup-monitor.service
@ -236,25 +245,26 @@ AccuracySec=1m
WantedBy=timers.target WantedBy=timers.target
EOF EOF
echo "✓ Created monitoring timer: $timer_file" echo "✓ Created monitoring timer: $timer_file"
} }
# Function to create the main monitoring script # Function to create the main monitoring script
create_monitoring_script() { create_monitoring_script() {
echo "" echo ""
echo "3. Creating PC Startup Monitor Script..." echo "3. Creating PC Startup Monitor Script..."
echo "======================================" echo "======================================"
local script_file="/usr/local/bin/pc-startup-check.sh" local script_file="/usr/local/bin/pc-startup-check.sh"
cat > "$script_file" << 'EOF' cat > "$script_file" << 'EOF'
#!/bin/bash #!/bin/bash
# PC Startup Time Monitor Check Script # PC Startup Time Monitor Check Script
# Monitors if PC was turned on during expected hours on specific days # Monitors if PC was turned on during expected hours on specific days
# Function to check if today is a monitored day # Function to check if today is a monitored day
is_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) # 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
@ -266,8 +276,9 @@ is_monitored_day() {
# Function to check if current time is between 5AM and 8AM # Function to check if current time is between 5AM and 8AM
is_current_time_in_window() { is_current_time_in_window() {
local current_hour=$(date +%H) local current_hour current_hour_num
local current_hour_num=$((10#$current_hour)) current_hour=$(date +%H)
current_hour_num=$((10#$current_hour))
if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
return 0 # Yes, current time is in the 5AM-8AM window 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 # Function to check if PC was booted between 5AM-8AM today
was_booted_in_window_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 # Calculate boot time from uptime
local uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0") local uptime_seconds
local boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S") 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 # 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 if [[ "$boot_date" != "$today" ]]; then
return 1 # Not booted today return 1 # Not booted today
fi fi
# Extract hour from boot time # Extract hour from boot time
local boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1) local boot_hour boot_hour_num
local boot_hour_num=$((10#$boot_hour)) 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 # Check if boot time was between 5AM and 8AM
if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then 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 # Function to show notification/warning
show_startup_warning() { show_startup_warning() {
local day_name=$(date +%A) local day_name current_time today
local current_time=$(date +"%H:%M") day_name=$(date +%A)
local today=$(date +%Y-%m-%d) current_time=$(date +"%H:%M")
today=$(date +%Y-%m-%d)
echo "⚠️ PC STARTUP TIME WARNING" echo "⚠️ PC STARTUP TIME WARNING"
echo "Date: $today ($day_name)" echo "Date: $today ($day_name)"
@ -346,19 +362,19 @@ else
fi fi
EOF EOF
chmod +x "$script_file" chmod +x "$script_file"
echo "✓ Created monitoring script: $script_file" echo "✓ Created monitoring script: $script_file"
} }
# Function to create management script # Function to create management script
create_management_script() { create_management_script() {
echo "" echo ""
echo "4. Creating Management Script..." echo "4. Creating Management Script..."
echo "==============================" echo "=============================="
local script_file="/usr/local/bin/pc-startup-monitor-manager.sh" local script_file="/usr/local/bin/pc-startup-monitor-manager.sh"
cat > "$script_file" << 'EOF' cat > "$script_file" << 'EOF'
#!/bin/bash #!/bin/bash
# PC Startup Monitor Manager # PC Startup Monitor Manager
# Provides easy management of the PC startup monitoring feature # Provides easy management of the PC startup monitoring feature
@ -421,150 +437,150 @@ case "$1" in
esac esac
EOF EOF
chmod +x "$script_file" chmod +x "$script_file"
echo "✓ Created management script: $script_file" echo "✓ Created management script: $script_file"
} }
# Function to enable the services # Function to enable the services
enable_services() { enable_services() {
echo "" echo ""
echo "5. Enabling PC Startup Monitor..." echo "5. Enabling PC Startup Monitor..."
echo "===============================" echo "==============================="
# Reload systemd daemon # Reload systemd daemon
systemctl daemon-reload systemctl daemon-reload
echo "✓ Reloaded systemd daemon" echo "✓ Reloaded systemd daemon"
# Enable and start the timer # Enable and start the timer
systemctl enable pc-startup-monitor.timer systemctl enable pc-startup-monitor.timer
echo "✓ Enabled pc-startup-monitor timer" echo "✓ Enabled pc-startup-monitor timer"
systemctl start pc-startup-monitor.timer systemctl start pc-startup-monitor.timer
echo "✓ Started pc-startup-monitor timer" echo "✓ Started pc-startup-monitor timer"
} }
# Function to test the setup # Function to test the setup
test_setup() { test_setup() {
echo "" echo ""
echo "6. Testing Setup..." echo "6. Testing Setup..."
echo "==================" echo "=================="
echo "Service files:" echo "Service files:"
if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then
echo "✓ Service file exists" echo "✓ Service file exists"
else else
echo "✗ Service file missing" echo "✗ Service file missing"
fi fi
if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then
echo "✓ Timer file exists" echo "✓ Timer file exists"
else else
echo "✗ Timer file missing" echo "✗ Timer file missing"
fi fi
echo "" echo ""
echo "Timer status:" 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" echo "✓ Timer is enabled"
else else
echo "✗ Timer is not enabled" echo "✗ Timer is not enabled"
fi 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" echo "✓ Timer is active"
else else
echo "✗ Timer is not active" echo "✗ Timer is not active"
fi fi
echo "" echo ""
echo "Testing current logic:" echo "Testing current logic:"
/usr/local/bin/pc-startup-check.sh /usr/local/bin/pc-startup-check.sh
} }
# Function to show final instructions # Function to show final instructions
show_instructions() { show_instructions() {
echo "" echo ""
echo "==========================================" echo "=========================================="
echo "PC Startup Monitor Setup Complete" echo "PC Startup Monitor Setup Complete"
echo "==========================================" echo "=========================================="
echo "Summary:" echo "Summary:"
echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)" echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)"
echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)" echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)"
echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)" echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)"
echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)" echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)"
echo "✓ Timer enabled and started" echo "✓ Timer enabled and started"
echo "" echo ""
echo "How it works:" echo "How it works:"
echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday" echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday"
echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM" echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM"
echo "• Checks daily at 8:30 AM if PC was turned on in expected window" echo "• Checks daily at 8:30 AM if PC was turned on in expected window"
echo "• Shows warning if PC was not turned on during expected time" echo "• Shows warning if PC was not turned on during expected time"
echo "" echo ""
echo "Management commands:" echo "Management commands:"
echo " sudo pc-startup-monitor-manager.sh status - Check status" echo " sudo pc-startup-monitor-manager.sh status - Check status"
echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs" echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs"
echo " sudo pc-startup-monitor-manager.sh test - Test monitor now" echo " sudo pc-startup-monitor-manager.sh test - Test monitor now"
echo "" echo ""
echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)" echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)"
echo "" echo ""
} }
# Function to prompt for confirmation # Function to prompt for confirmation
confirm_setup() { confirm_setup() {
echo "" echo ""
echo "PC Startup Monitor Setup" echo "PC Startup Monitor Setup"
echo "=======================" echo "======================="
echo "This will set up monitoring for PC startup times." echo "This will set up monitoring for PC startup times."
echo "" echo ""
echo "Monitoring schedule:" echo "Monitoring schedule:"
echo "- Days: Monday, Friday, Saturday, Sunday" echo "- Days: Monday, Friday, Saturday, Sunday"
echo "- Expected startup time: 5:00 AM - 8:00 AM" echo "- Expected startup time: 5:00 AM - 8:00 AM"
echo "- Check time: 8:30 AM daily" echo "- Check time: 8:30 AM daily"
echo "- Action: Show warning if PC wasn't started in expected window" echo "- Action: Show warning if PC wasn't started in expected window"
echo "" echo ""
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
read -p "Do you want to proceed? (y/N): " confirm read -r -p "Do you want to proceed? (y/N): " confirm
case "$confirm" in case "$confirm" in
[yY]|[yY][eE][sS]) [yY] | [yY][eE][sS])
echo "Proceeding with setup..."
return 0
;;
*)
echo "Setup cancelled."
exit 0
;;
esac
else
echo "Auto-proceeding with setup (use --interactive to prompt)"
echo "Proceeding with setup..." echo "Proceeding with setup..."
return 0 return 0
fi ;;
*)
echo "Setup cancelled."
exit 0
;;
esac
else
echo "Auto-proceeding with setup (use --interactive to prompt)"
echo "Proceeding with setup..."
return 0
fi
} }
# Main execution flow # Main execution flow
main() { main() {
# Check for sudo privileges # Check for sudo privileges
check_sudo "$@" check_sudo "$@"
# Confirm setup # Confirm setup
confirm_setup confirm_setup
# Create all components # Create all components
create_monitoring_service create_monitoring_service
create_monitoring_timer create_monitoring_timer
create_monitoring_script create_monitoring_script
create_management_script create_management_script
# Enable services # Enable services
enable_services enable_services
# Test setup # Test setup
test_setup test_setup
# Show instructions # Show instructions
show_instructions show_instructions
} }
# Run main function # Run main function

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 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() { usage() {
cat <<'EOF' cat << 'EOF'
Usage: control_from_mobile.sh <command> [options] Usage: control_from_mobile.sh <command> [options]
Commands: Commands:
@ -46,118 +46,117 @@ EOF
} }
log() { log() {
printf '[%s] %s\n' "$SCRIPT_NAME" "$*" printf '[%s] %s\n' "$SCRIPT_NAME" "$*"
} }
warn() { warn() {
printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2 printf '[%s] %s\n' "$SCRIPT_NAME" "$*" >&2
} }
die() { die() {
warn "$*" warn "$*"
exit 1 exit 1
} }
require_non_root() { 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." die "Run this script as a regular desktop user, not root."
fi fi
} }
prompt_yes_no() { prompt_yes_no() {
local prompt="$1" local prompt="$1"
local reply local reply
read -r -p "$prompt [y/N]: " reply read -r -p "$prompt [y/N]: " reply
case "$reply" in case "$reply" in
[Yy][Ee][Ss]|[Yy]) return 0 ;; [Yy][Ee][Ss] | [Yy]) return 0 ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
ensure_directories() { ensure_directories() {
mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR" mkdir -p "$CONFIG_DIR" "$STATE_DIR" "$SYSTEMD_USER_DIR"
chmod 700 "$CONFIG_DIR" chmod 700 "$CONFIG_DIR"
} }
missing_commands() { missing_commands() {
local missing=() local missing=()
for cmd in "$@"; do for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then if ! command -v "$cmd" > /dev/null 2>&1; then
missing+=("$cmd") missing+=("$cmd")
fi fi
done done
printf '%s\n' "${missing[@]-}" printf '%s\n' "${missing[@]-}"
} }
install_dependencies() { 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." die "systemctl not found. Install systemd before running this script."
fi fi
local required=(x11vnc qrencode ssh) local required=(x11vnc qrencode ssh)
local needed=() local needed=()
mapfile -t needed < <(missing_commands "${required[@]}") mapfile -t needed < <(missing_commands "${required[@]}")
if (( ${#needed[@]} == 0 )); then if ((${#needed[@]} == 0)); then
log "All required packages (${required[*]}) are present." log "All required packages (${required[*]}) are present."
return return
fi fi
if command -v pacman >/dev/null 2>&1; then if command -v pacman > /dev/null 2>&1; then
log "Installing missing packages: ${needed[*]}" log "Installing missing packages: ${needed[*]}"
sudo pacman -S --needed --noconfirm "${needed[@]}" sudo pacman -S --needed --noconfirm "${needed[@]}"
else else
die "Missing commands (${needed[*]}). Install them manually and rerun setup." die "Missing commands (${needed[*]}). Install them manually and rerun setup."
fi fi
} }
create_password_file() { create_password_file() {
local force=${1:-0} local force=${1:-0}
if [[ -f "$PASSWORD_FILE" && "$force" -ne 1 ]]; if [[ -f $PASSWORD_FILE && $force -ne 1 ]]; then
then log "Using existing VNC password file at $PASSWORD_FILE"
log "Using existing VNC password file at $PASSWORD_FILE" return
return fi
fi
if [[ -f "$PASSWORD_FILE" ]]; then if [[ -f $PASSWORD_FILE ]]; then
if ! prompt_yes_no "Regenerate the stored VNC password?"; then if ! prompt_yes_no "Regenerate the stored VNC password?"; then
log "Keeping existing password." log "Keeping existing password."
return return
fi fi
fi fi
local password confirm generated=0 local password confirm generated=0
read -rsp "Enter VNC password (leave blank to auto-generate): " password read -rsp "Enter VNC password (leave blank to auto-generate): " password
printf '\n' printf '\n'
if [[ -z "$password" ]]; then if [[ -z $password ]]; then
generated=1 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" log "Generated VNC password: $password"
else else
read -rsp "Confirm password: " confirm read -rsp "Confirm password: " confirm
printf '\n' printf '\n'
if [[ "$password" != "$confirm" ]]; then if [[ $password != "$confirm" ]]; then
die "Passwords do not match." die "Passwords do not match."
fi fi
fi fi
local tmp local tmp
tmp=$(mktemp) tmp=$(mktemp)
x11vnc -storepasswd "$password" "$tmp" >/dev/null x11vnc -storepasswd "$password" "$tmp" > /dev/null
install -m 600 "$tmp" "$PASSWORD_FILE" install -m 600 "$tmp" "$PASSWORD_FILE"
rm -f "$tmp" rm -f "$tmp"
if (( generated == 0 )); then if ((generated == 0)); then
log "Password stored securely at $PASSWORD_FILE (hashed)." log "Password stored securely at $PASSWORD_FILE (hashed)."
else else
log "Please write down the generated password; it will be needed on your Android device." log "Please write down the generated password; it will be needed on your Android device."
fi fi
} }
create_env_file() { create_env_file() {
if [[ -f "$ENV_FILE" ]]; then if [[ -f $ENV_FILE ]]; then
return return
fi fi
cat >"$ENV_FILE" <<EOF cat > "$ENV_FILE" << EOF
# control-from-mobile configuration # control-from-mobile configuration
# Adjust these values if needed and rerun: systemctl --user restart $SERVICE_NAME # Adjust these values if needed and rerun: systemctl --user restart $SERVICE_NAME
X11_DISPLAY="$DEFAULT_DISPLAY" X11_DISPLAY="$DEFAULT_DISPLAY"
@ -165,11 +164,11 @@ VNC_PORT="$DEFAULT_PORT"
# Use 127.0.0.1 to force SSH tunnel-only access, or 0.0.0.0 to expose on LAN. # Use 127.0.0.1 to force SSH tunnel-only access, or 0.0.0.0 to expose on LAN.
VNC_BIND_ADDR="$DEFAULT_BIND_ADDR" VNC_BIND_ADDR="$DEFAULT_BIND_ADDR"
EOF EOF
chmod 600 "$ENV_FILE" chmod 600 "$ENV_FILE"
} }
create_runner_script() { create_runner_script() {
cat >"$RUNNER_FILE" <<'EOF' cat > "$RUNNER_FILE" << 'EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
@ -209,11 +208,11 @@ exec /usr/bin/x11vnc \
-ncache_cr \ -ncache_cr \
-o "$LOG_FILE" -o "$LOG_FILE"
EOF EOF
chmod 700 "$RUNNER_FILE" chmod 700 "$RUNNER_FILE"
} }
create_service_file() { create_service_file() {
cat >"$SERVICE_FILE" <<EOF cat > "$SERVICE_FILE" << EOF
[Unit] [Unit]
Description=Expose X11 desktop over VNC for Android control Description=Expose X11 desktop over VNC for Android control
After=graphical-session.target After=graphical-session.target
@ -234,183 +233,183 @@ EOF
} }
reload_user_daemon() { reload_user_daemon() {
systemctl --user daemon-reload systemctl --user daemon-reload
} }
ensure_service_present() { 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" die "Service files missing. Run: $SCRIPT_NAME setup"
fi fi
} }
start_service() { start_service() {
ensure_service_present ensure_service_present
systemctl --user start "$SERVICE_NAME" systemctl --user start "$SERVICE_NAME"
} }
stop_service() { stop_service() {
systemctl --user stop "$SERVICE_NAME" || true systemctl --user stop "$SERVICE_NAME" || true
} }
status_service() { status_service() {
if systemctl --user is-active --quiet "$SERVICE_NAME"; then if systemctl --user is-active --quiet "$SERVICE_NAME"; then
log "Service is active." log "Service is active."
else else
log "Service is inactive." log "Service is inactive."
fi fi
systemctl --user status "$SERVICE_NAME" --no-pager || true systemctl --user status "$SERVICE_NAME" --no-pager || true
} }
enable_service() { enable_service() {
ensure_service_present ensure_service_present
systemctl --user enable "$SERVICE_NAME" systemctl --user enable "$SERVICE_NAME"
} }
disable_service() { disable_service() {
systemctl --user disable "$SERVICE_NAME" || true systemctl --user disable "$SERVICE_NAME" || true
} }
show_info() { show_info() {
ensure_service_present ensure_service_present
# shellcheck disable=SC1090 # shellcheck disable=SC1090
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE" [[ -f $ENV_FILE ]] && source "$ENV_FILE"
local port="${VNC_PORT:-$DEFAULT_PORT}" local port="${VNC_PORT:-$DEFAULT_PORT}"
local bind_addr="${VNC_BIND_ADDR:-$DEFAULT_BIND_ADDR}" local bind_addr="${VNC_BIND_ADDR:-$DEFAULT_BIND_ADDR}"
local display="${X11_DISPLAY:-$DEFAULT_DISPLAY}" local display="${X11_DISPLAY:-$DEFAULT_DISPLAY}"
local is_active="inactive" local is_active="inactive"
if systemctl --user is-active --quiet "$SERVICE_NAME"; then if systemctl --user is-active --quiet "$SERVICE_NAME"; then
is_active="active" is_active="active"
fi fi
log "Service status: $is_active" log "Service status: $is_active"
log "Display: $display" log "Display: $display"
log "Listening address: $bind_addr" log "Listening address: $bind_addr"
log "VNC port: $port" log "VNC port: $port"
log "Password file: $PASSWORD_FILE" log "Password file: $PASSWORD_FILE"
local -a ip_list=() 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 while IFS= read -r line; do
[[ -z "$line" ]] && continue [[ -z $line ]] && continue
ip_list+=("$line") 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 fi
if (( ${#ip_list[@]} > 0 )); then if ((${#ip_list[@]} > 0)); then
log "Detected LAN IPs:" log "Detected LAN IPs:"
for ip in "${ip_list[@]}"; do for ip in "${ip_list[@]}"; do
printf ' - %s\n' "$ip" printf ' - %s\n' "$ip"
done done
else else
warn "Could not detect LAN IPs." warn "Could not detect LAN IPs."
fi fi
printf '\nRecommended Android clients (FOSS):\n' printf '\nRecommended Android clients (FOSS):\n'
printf ' • bVNC (available on F-Droid) — supports full control.\n' printf ' • bVNC (available on F-Droid) — supports full control.\n'
printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n' printf ' • Termux + OpenSSH for establishing an SSH tunnel when exposing only on 127.0.0.1.\n'
printf '\nConnect via VNC:\n' printf '\nConnect via VNC:\n'
printf ' Host: <your-ip>\n Port: %s\n Password: <stored during setup>\n' "$port" printf ' Host: <your-ip>\n Port: %s\n Password: <stored during setup>\n' "$port"
local qr_host local qr_host
if (( ${#ip_list[@]} > 0 )); then if ((${#ip_list[@]} > 0)); then
qr_host="${ip_list[0]}" qr_host="${ip_list[0]}"
else else
qr_host="$bind_addr" 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" qr_host="127.0.0.1"
fi fi
warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed." warn "Using fallback host $qr_host for QR code; replace with an accessible IP if needed."
fi 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" printf '\nConnection QR (vnc://%s:%s):\n' "$qr_host" "$port"
qrencode -o - "vnc://$qr_host:$port" -t ASCII || true qrencode -o - "vnc://$qr_host:$port" -t ASCII || true
else else
warn "qrencode not found; reinstall qrencode to get QR codes." warn "qrencode not found; reinstall qrencode to get QR codes."
fi fi
printf '\nFor encrypted access outside your LAN, use Termux on Android:\n' printf '\nFor encrypted access outside your LAN, use Termux on Android:\n'
printf ' ssh -L %s:localhost:%s <user>@<public-ip>\n' "$port" "$port" printf ' ssh -L %s:localhost:%s <user>@<public-ip>\n' "$port" "$port"
printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port" printf 'Then point bVNC to 127.0.0.1:%s.\n' "$port"
} }
uninstall_files() { uninstall_files() {
local purge_password=${1:-0} local purge_password=${1:-0}
stop_service stop_service
disable_service disable_service
rm -f "$SERVICE_FILE" rm -f "$SERVICE_FILE"
rm -f "$RUNNER_FILE" rm -f "$RUNNER_FILE"
rm -f "$ENV_FILE" rm -f "$ENV_FILE"
if (( purge_password )); then if ((purge_password)); then
rm -f "$PASSWORD_FILE" rm -f "$PASSWORD_FILE"
log "Removed password file." log "Removed password file."
fi fi
reload_user_daemon reload_user_daemon
log "Removed generated files." log "Removed generated files."
} }
main() { main() {
require_non_root require_non_root
local cmd="${1:-}" local cmd="${1:-}"
shift || true shift || true
case "$cmd" in case "$cmd" in
setup) setup)
local force=0 local force=0
if [[ "${1:-}" == "--force-password" ]]; then if [[ ${1:-} == "--force-password" ]]; then
force=1 force=1
shift || true shift || true
fi fi
ensure_directories ensure_directories
install_dependencies install_dependencies
create_password_file "$force" create_password_file "$force"
create_env_file create_env_file
create_runner_script create_runner_script
create_service_file create_service_file
reload_user_daemon reload_user_daemon
log "Setup complete. Start the service with: $SCRIPT_NAME start" log "Setup complete. Start the service with: $SCRIPT_NAME start"
;; ;;
start) start)
start_service start_service
show_info show_info
;; ;;
stop) stop)
stop_service stop_service
;; ;;
restart) restart)
stop_service stop_service
start_service start_service
;; ;;
status) status)
status_service status_service
;; ;;
enable) enable)
enable_service enable_service
;; ;;
disable) disable)
disable_service disable_service
;; ;;
info) info)
show_info show_info
;; ;;
uninstall) uninstall)
local purge=0 local purge=0
if [[ "${1:-}" == "--purge" ]]; then if [[ ${1:-} == "--purge" ]]; then
purge=1 purge=1
shift || true shift || true
fi fi
uninstall_files "$purge" uninstall_files "$purge"
;; ;;
help|--help|-h|"" ) help | --help | -h | "")
usage usage
;; ;;
*) *)
usage usage
die "Unknown command: $cmd" die "Unknown command: $cmd"
;; ;;
esac esac
} }
main "$@" main "$@"

View File

@ -3,15 +3,15 @@
# Handles installation, startup, autostart, and i3blocks status # Handles installation, startup, autostart, and i3blocks status
# Handles sudo privileges automatically # Handles sudo privileges automatically
set -e # Exit on any error set -e # Exit on any error
# Function to check and request sudo privileges for package installation # Function to check and request sudo privileges for package installation
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]] && [[ "$1" == "install" ]]; then if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
echo "Package installation requires sudo privileges." echo "Package installation requires sudo privileges."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
echo "ActivityWatch Setup for Arch Linux + i3" echo "ActivityWatch Setup for Arch Linux + i3"
@ -20,12 +20,12 @@ echo "Current Date: $(date)"
echo "User: ${SUDO_USER:-$USER}" echo "User: ${SUDO_USER:-$USER}"
# Get the actual user (even when running with sudo) # Get the actual user (even when running with sudo)
if [[ -n "$SUDO_USER" ]]; then if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER" ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER" USER_HOME="/home/$SUDO_USER"
else else
ACTUAL_USER="$USER" ACTUAL_USER="$USER"
USER_HOME="$HOME" USER_HOME="$HOME"
fi fi
echo "Target user: $ACTUAL_USER" echo "Target user: $ACTUAL_USER"
@ -33,180 +33,180 @@ echo "User home: $USER_HOME"
# Function to check if ActivityWatch is installed # Function to check if ActivityWatch is installed
check_activitywatch_installed() { check_activitywatch_installed() {
echo "" echo ""
echo "1. Checking ActivityWatch Installation..." echo "1. Checking ActivityWatch Installation..."
echo "========================================" echo "========================================"
# Check if activitywatch-bin is installed via pacman # 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" echo "✓ activitywatch-bin package is installed"
return 0 return 0
fi
# Check if aw-qt binary exists in common locations
local common_paths=(
"/usr/bin/aw-qt"
"/usr/local/bin/aw-qt"
"$USER_HOME/.local/bin/aw-qt"
"$USER_HOME/activitywatch/aw-qt"
)
for path in "${common_paths[@]}"; do
if [[ -x $path ]]; then
echo "✓ ActivityWatch found at: $path"
return 0
fi fi
done
# Check if aw-qt binary exists in common locations echo "✗ ActivityWatch not found"
local common_paths=( return 1
"/usr/bin/aw-qt"
"/usr/local/bin/aw-qt"
"$USER_HOME/.local/bin/aw-qt"
"$USER_HOME/activitywatch/aw-qt"
)
for path in "${common_paths[@]}"; do
if [[ -x "$path" ]]; then
echo "✓ ActivityWatch found at: $path"
return 0
fi
done
echo "✗ ActivityWatch not found"
return 1
} }
# Function to install ActivityWatch # Function to install ActivityWatch
install_activitywatch() { install_activitywatch() {
echo "" echo ""
echo "2. Installing ActivityWatch..." echo "2. Installing ActivityWatch..."
echo "=============================" echo "============================="
# Check if we need sudo for installation # Check if we need sudo for installation
check_sudo "install" check_sudo "install"
echo "Installing activitywatch-bin from AUR..." echo "Installing activitywatch-bin from AUR..."
# Check if an AUR helper is available # Check if an AUR helper is available
local aur_helpers=("yay" "paru" "makepkg") local aur_helpers=("yay" "paru" "makepkg")
local helper_found="" local helper_found=""
for helper in "${aur_helpers[@]}"; do for helper in "${aur_helpers[@]}"; do
if command -v "$helper" &>/dev/null; then if command -v "$helper" &> /dev/null; then
helper_found="$helper" helper_found="$helper"
break break
fi
done
if [[ -n "$helper_found" && "$helper_found" != "makepkg" ]]; then
echo "Using AUR helper: $helper_found"
if [[ $EUID -eq 0 ]]; then
# Running as root, need to install as user
sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin
else
"$helper_found" -S --noconfirm activitywatch-bin
fi
else
echo "No AUR helper found. Installing manually with makepkg..."
install_activitywatch_manual
fi fi
done
echo "✓ ActivityWatch installation completed" if [[ -n $helper_found && $helper_found != "makepkg" ]]; then
echo "Using AUR helper: $helper_found"
if [[ $EUID -eq 0 ]]; then
# Running as root, need to install as user
sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin
else
"$helper_found" -S --noconfirm activitywatch-bin
fi
else
echo "No AUR helper found. Installing manually with makepkg..."
install_activitywatch_manual
fi
echo "✓ ActivityWatch installation completed"
} }
# Function to manually install ActivityWatch via makepkg # Function to manually install ActivityWatch via makepkg
install_activitywatch_manual() { install_activitywatch_manual() {
local temp_dir="/tmp/activitywatch-install" local temp_dir="/tmp/activitywatch-install"
local original_user="$ACTUAL_USER" local original_user="$ACTUAL_USER"
# Create temp directory # Create temp directory
mkdir -p "$temp_dir" mkdir -p "$temp_dir"
cd "$temp_dir" cd "$temp_dir"
# Download PKGBUILD # 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 . sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
else else
echo "Installing git..." echo "Installing git..."
pacman -S --noconfirm git pacman -S --noconfirm git
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
fi fi
# Build and install package # Build and install package
sudo -u "$original_user" makepkg -si --noconfirm sudo -u "$original_user" makepkg -si --noconfirm
# Cleanup # Cleanup
cd / cd /
rm -rf "$temp_dir" rm -rf "$temp_dir"
} }
# Function to check if ActivityWatch is running # Function to check if ActivityWatch is running
check_activitywatch_running() { check_activitywatch_running() {
echo "" echo ""
echo "3. Checking ActivityWatch Status..." echo "3. Checking ActivityWatch Status..."
echo "==================================" echo "=================================="
# Check for aw-qt process # 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" echo "✓ ActivityWatch (aw-qt) is running"
return 0 return 0
fi fi
# Check for aw-server process # 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" echo "✓ ActivityWatch server is running"
return 0 return 0
fi fi
echo "✗ ActivityWatch is not running" echo "✗ ActivityWatch is not running"
return 1 return 1
} }
# Function to start ActivityWatch # Function to start ActivityWatch
start_activitywatch() { start_activitywatch() {
echo "" echo ""
echo "4. Starting ActivityWatch..." echo "4. Starting ActivityWatch..."
echo "===========================" echo "==========================="
# Find aw-qt executable # Find aw-qt executable
local aw_qt_path="" 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)" aw_qt_path="$(which aw-qt)"
elif [[ -x "/usr/bin/aw-qt" ]]; then elif [[ -x "/usr/bin/aw-qt" ]]; then
aw_qt_path="/usr/bin/aw-qt" aw_qt_path="/usr/bin/aw-qt"
else else
echo "✗ Could not find aw-qt executable" echo "✗ Could not find aw-qt executable"
return 1 return 1
fi fi
echo "Starting ActivityWatch as user: $ACTUAL_USER" echo "Starting ActivityWatch as user: $ACTUAL_USER"
echo "Using aw-qt from: $aw_qt_path" echo "Using aw-qt from: $aw_qt_path"
# Start as the actual user in the background # Start as the actual user in the background
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
# Running as root, start as user # Running as root, start as user
sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" & sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" &
else else
# Running as user # Running as user
"$aw_qt_path" & "$aw_qt_path" &
fi fi
# Give it time to start # Give it time to start
sleep 3 sleep 3
if check_activitywatch_running >/dev/null 2>&1; then if check_activitywatch_running > /dev/null 2>&1; then
echo "✓ ActivityWatch started successfully" echo "✓ ActivityWatch started successfully"
else else
echo "! ActivityWatch may be starting (check system tray)" echo "! ActivityWatch may be starting (check system tray)"
fi fi
} }
# Function to setup autostart # Function to setup autostart
setup_autostart() { setup_autostart() {
echo "" echo ""
echo "5. Setting Up Autostart..." echo "5. Setting Up Autostart..."
echo "=========================" echo "========================="
local autostart_dir="$USER_HOME/.config/autostart" local autostart_dir="$USER_HOME/.config/autostart"
local desktop_file="$autostart_dir/activitywatch.desktop" local desktop_file="$autostart_dir/activitywatch.desktop"
local i3_config="$USER_HOME/.config/i3/config" local i3_config="$USER_HOME/.config/i3/config"
# Method 1: XDG Autostart (works with most desktop environments) # Method 1: XDG Autostart (works with most desktop environments)
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir" sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir"
else else
mkdir -p "$autostart_dir" mkdir -p "$autostart_dir"
fi fi
# Create desktop file for autostart # Create desktop file for autostart
cat > "$desktop_file" << EOF cat > "$desktop_file" << EOF
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name=ActivityWatch Name=ActivityWatch
@ -221,58 +221,60 @@ Terminal=false
Categories=Utility; Categories=Utility;
EOF EOF
# Set proper ownership if running as root # Set proper ownership if running as root
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file" chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file"
fi fi
echo "✓ Created XDG autostart entry: $desktop_file" echo "✓ Created XDG autostart entry: $desktop_file"
# Method 2: i3 config autostart (specific to i3) # Method 2: i3 config autostart (specific to i3)
if [[ -f "$i3_config" ]]; then if [[ -f $i3_config ]]; then
# Check if autostart entry already exists # Check if autostart entry already exists
if ! grep -q "aw-qt" "$i3_config"; then if ! grep -q "aw-qt" "$i3_config"; then
# Add autostart entry to i3 config # 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 "cat <<'EOF' >> '$i3_config'
if [[ $EUID -eq 0 ]]; then # Auto-start ActivityWatch
# Running as root exec --no-startup-id aw-qt
sudo -u "$ACTUAL_USER" bash -c "echo '' >> '$i3_config'" EOF"
sudo -u "$ACTUAL_USER" bash -c "echo '# Auto-start ActivityWatch' >> '$i3_config'" else
sudo -u "$ACTUAL_USER" bash -c "echo 'exec --no-startup-id aw-qt' >> '$i3_config'" {
else printf '\n'
echo "" >> "$i3_config" printf '# Auto-start ActivityWatch\n'
echo "# Auto-start ActivityWatch" >> "$i3_config" printf 'exec --no-startup-id aw-qt\n'
echo "exec --no-startup-id aw-qt" >> "$i3_config" } >> "$i3_config"
fi fi
echo "✓ Added ActivityWatch to i3 config autostart" echo "✓ Added ActivityWatch to i3 config autostart"
else
echo "✓ ActivityWatch autostart already exists in i3 config"
fi
else else
echo "! i3 config not found at $i3_config" echo "✓ ActivityWatch autostart already exists in i3 config"
fi fi
else
echo "! i3 config not found at $i3_config"
fi
} }
# Function to create i3blocks status script # Function to create i3blocks status script
create_i3blocks_status() { create_i3blocks_status() {
echo "" echo ""
echo "6. Creating i3blocks Status Script..." echo "6. Creating i3blocks Status Script..."
echo "====================================" echo "===================================="
local i3blocks_dir="$USER_HOME/.config/i3blocks" local i3blocks_dir="$USER_HOME/.config/i3blocks"
local status_script="$i3blocks_dir/activitywatch_status.sh" local status_script="$i3blocks_dir/activitywatch_status.sh"
# Create i3blocks directory if it doesn't exist # Create i3blocks directory if it doesn't exist
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir" sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir"
else else
mkdir -p "$i3blocks_dir" mkdir -p "$i3blocks_dir"
fi fi
# Create the status script # Create the status script
cat > "$status_script" << 'EOF' cat > "$status_script" << 'EOF'
#!/bin/bash #!/bin/bash
# ActivityWatch status script for i3blocks # ActivityWatch status script for i3blocks
# Shows ActivityWatch installation and running status # Shows ActivityWatch installation and running status
@ -323,134 +325,134 @@ else
fi fi
EOF EOF
chmod +x "$status_script" chmod +x "$status_script"
# Set proper ownership if running as root # Set proper ownership if running as root
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script" chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script"
fi fi
echo "✓ Created i3blocks status script: $status_script" echo "✓ Created i3blocks status script: $status_script"
# Show configuration instructions # Show configuration instructions
echo "" echo ""
echo "To add to your i3blocks config, add this block:" echo "To add to your i3blocks config, add this block:"
echo "" echo ""
echo "[activitywatch]" echo "[activitywatch]"
echo "command=~/.config/i3blocks/activitywatch_status.sh" echo "command=~/.config/i3blocks/activitywatch_status.sh"
echo "interval=10" echo "interval=10"
echo "color=#FFFFFF" echo "color=#FFFFFF"
echo "" echo ""
} }
# Function to test the setup # Function to test the setup
test_setup() { test_setup() {
echo "" echo ""
echo "7. Testing Setup..." echo "7. Testing Setup..."
echo "==================" echo "=================="
echo "Installation status:" 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" echo "✓ ActivityWatch is installed"
else else
echo "✗ ActivityWatch is not installed" echo "✗ ActivityWatch is not installed"
fi fi
echo "Running status:" 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" echo "✓ ActivityWatch is running"
else else
echo "✗ ActivityWatch is not running" echo "✗ ActivityWatch is not running"
fi fi
echo "Autostart files:" echo "Autostart files:"
if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then
echo "✓ XDG autostart file exists" echo "✓ XDG autostart file exists"
else else
echo "✗ XDG autostart file missing" echo "✗ XDG autostart file missing"
fi fi
if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then
echo "✓ i3 autostart configured" echo "✓ i3 autostart configured"
else else
echo "! i3 autostart may not be configured" echo "! i3 autostart may not be configured"
fi fi
echo "i3blocks status script:" echo "i3blocks status script:"
if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then
echo "✓ i3blocks status script created" echo "✓ i3blocks status script created"
echo "Testing status script:" echo "Testing status script:"
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh" sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh"
else
"$USER_HOME/.config/i3blocks/activitywatch_status.sh"
fi
else else
echo "✗ i3blocks status script missing" "$USER_HOME/.config/i3blocks/activitywatch_status.sh"
fi fi
else
echo "✗ i3blocks status script missing"
fi
} }
# Function to show final instructions # Function to show final instructions
show_instructions() { show_instructions() {
echo "" echo ""
echo "==========================================" echo "=========================================="
echo "ActivityWatch Setup Complete" echo "ActivityWatch Setup Complete"
echo "==========================================" echo "=========================================="
echo "Summary:" echo "Summary:"
echo "✓ ActivityWatch installation checked/completed" echo "✓ ActivityWatch installation checked/completed"
echo "✓ ActivityWatch startup configured" echo "✓ ActivityWatch startup configured"
echo "✓ Autostart configured (XDG + i3)" echo "✓ Autostart configured (XDG + i3)"
echo "✓ i3blocks status script created" echo "✓ i3blocks status script created"
echo "" echo ""
echo "Next steps:" echo "Next steps:"
echo "1. Add the i3blocks configuration to your config file:" echo "1. Add the i3blocks configuration to your config file:"
echo " ~/.config/i3blocks/config" echo " ~/.config/i3blocks/config"
echo "" echo ""
echo "2. Reload i3 configuration:" echo "2. Reload i3 configuration:"
echo " Super+Shift+R" echo " Super+Shift+R"
echo "" echo ""
echo "3. ActivityWatch web interface should be available at:" echo "3. ActivityWatch web interface should be available at:"
echo " http://localhost:5600" echo " http://localhost:5600"
echo "" echo ""
echo "4. Check system tray for ActivityWatch icon" echo "4. Check system tray for ActivityWatch icon"
echo "" echo ""
echo "Files created:" echo "Files created:"
echo " ~/.config/autostart/activitywatch.desktop" echo " ~/.config/autostart/activitywatch.desktop"
echo " ~/.config/i3blocks/activitywatch_status.sh" echo " ~/.config/i3blocks/activitywatch_status.sh"
echo " ~/.config/i3/config (modified)" echo " ~/.config/i3/config (modified)"
echo "" echo ""
} }
# Main execution flow # Main execution flow
main() { main() {
local need_install=false local need_install=false
local need_start=false local need_start=false
# Check installation # Check installation
if ! check_activitywatch_installed; then if ! check_activitywatch_installed; then
need_install=true need_install=true
fi fi
# Install if needed # Install if needed
if [[ "$need_install" == true ]]; then if [[ $need_install == true ]]; then
install_activitywatch install_activitywatch
fi fi
# Check if running # Check if running
if ! check_activitywatch_running; then if ! check_activitywatch_running; then
need_start=true need_start=true
fi fi
# Start if needed # Start if needed
if [[ "$need_start" == true ]]; then if [[ $need_start == true ]]; then
start_activitywatch start_activitywatch
fi fi
# Always set up autostart and i3blocks (in case they're missing) # Always set up autostart and i3blocks (in case they're missing)
setup_autostart setup_autostart
create_i3blocks_status create_i3blocks_status
test_setup test_setup
show_instructions show_instructions
} }
# Run main function # Run main function

View File

@ -3,194 +3,194 @@
set -euo pipefail set -euo pipefail
on_error() { on_error() {
local exit_code=$? local exit_code=$?
local line_number=$1 local line_number=$1
printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2 printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2
} }
trap 'on_error ${LINENO}' ERR trap 'on_error ${LINENO}' ERR
log_info() { log_info() {
printf '\033[1;34m[INFO]\033[0m %s\n' "$*" printf '\033[1;34m[INFO]\033[0m %s\n' "$*"
} }
log_warn() { log_warn() {
printf '\033[1;33m[WARN]\033[0m %s\n' "$*" printf '\033[1;33m[WARN]\033[0m %s\n' "$*"
} }
log_error() { log_error() {
printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2
exit 1 exit 1
} }
require_root() { 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)." log_error "This script must be run as root (try again with sudo)."
fi fi
} }
require_pacman() { 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." log_error "pacman not found. This script is intended for Arch Linux systems."
fi fi
} }
detect_kernel_release() { detect_kernel_release() {
uname -r uname -r
} }
select_host_package() { select_host_package() {
local kernel_release=$1 local kernel_release=$1
case "${kernel_release}" in case "${kernel_release}" in
*-lts) *-lts)
echo "virtualbox-host-modules-lts" echo "virtualbox-host-modules-lts"
;; ;;
*-arch*) *-arch*)
echo "virtualbox-host-modules-arch" echo "virtualbox-host-modules-arch"
;; ;;
*) *)
echo "virtualbox-host-dkms" echo "virtualbox-host-dkms"
;; ;;
esac esac
} }
collect_kernel_headers() { collect_kernel_headers() {
local -a headers=() local -a headers=()
local kernel_pkg header_pkg local kernel_pkg header_pkg
for kernel_pkg in linux linux-lts linux-zen linux-hardened; do 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" header_pkg="${kernel_pkg}-headers"
headers+=("${header_pkg}") headers+=("${header_pkg}")
fi fi
done done
if [[ ${#headers[@]} -gt 0 ]]; then if [[ ${#headers[@]} -gt 0 ]]; then
printf '%s\n' "${headers[@]}" printf '%s\n' "${headers[@]}"
fi fi
} }
maybe_remove_conflicting_host_packages() { maybe_remove_conflicting_host_packages() {
local selected_package=$1 local selected_package=$1
local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts")
local pkg local pkg
for pkg in "${candidates[@]}"; do 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}." log_warn "Removing conflicting package ${pkg} before installing ${selected_package}."
pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}"
fi fi
done done
} }
install_packages() { install_packages() {
local -a packages=() local -a packages=()
local -a headers=() local -a headers=()
local host_package=$1 local host_package=$1
shift shift
if [[ $# -gt 0 ]]; then if [[ $# -gt 0 ]]; then
mapfile -t headers < <(printf '%s\n' "$@" | sort -u) mapfile -t headers < <(printf '%s\n' "$@" | sort -u)
fi fi
packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}")
if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
packages+=("dkms") packages+=("dkms")
fi fi
if [[ ${#headers[@]} -gt 0 ]]; then if [[ ${#headers[@]} -gt 0 ]]; then
packages+=("${headers[@]}") packages+=("${headers[@]}")
fi fi
log_info "Installing packages: ${packages[*]}" log_info "Installing packages: ${packages[*]}"
pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}"
} }
rebuild_virtualbox_modules() { rebuild_virtualbox_modules() {
local host_package=$1 local host_package=$1
if [[ "${host_package}" == "virtualbox-host-dkms" ]]; then if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
if command -v dkms >/dev/null 2>&1; then if command -v dkms > /dev/null 2>&1; then
log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." log_info "Rebuilding VirtualBox DKMS modules for all installed kernels."
dkms autoinstall dkms autoinstall
else else
log_warn "dkms command not found; skipping DKMS rebuild." log_warn "dkms command not found; skipping DKMS rebuild."
fi fi
fi fi
} }
reload_virtualbox_modules() { reload_virtualbox_modules() {
log_info "Loading VirtualBox kernel modules." log_info "Loading VirtualBox kernel modules."
if [[ -x /sbin/rcvboxdrv ]]; then if [[ -x /sbin/rcvboxdrv ]]; then
/sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules."
elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then
/usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules."
fi fi
local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci)
local mod local mod
for mod in "${modules[@]}"; do for mod in "${modules[@]}"; do
if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then 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." log_warn "Module ${mod} failed to load; check dmesg for details."
fi fi
fi fi
done done
if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then
log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues."
fi fi
log_info "VirtualBox kernel driver loaded successfully." log_info "VirtualBox kernel driver loaded successfully."
} }
warn_if_secure_boot_enabled() { warn_if_secure_boot_enabled() {
local secure_boot_file local secure_boot_file
if [[ -d /sys/firmware/efi/efivars ]]; then 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) 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 if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then
local state local state
state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2>/dev/null || echo "0") state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2> /dev/null || echo "0")
if [[ "${state}" == "1" ]]; then if [[ ${state} == "1" ]]; then
log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually."
fi fi
fi fi
fi fi
} }
remind_group_membership() { remind_group_membership() {
local invoking_user=${SUDO_USER:-} 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 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" log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers"
else else
log_info "User ${invoking_user} is already in the vboxusers group." log_info "User ${invoking_user} is already in the vboxusers group."
fi fi
fi fi
} }
main() { main() {
require_root require_root
require_pacman require_pacman
PACMAN_INSTALL_FLAGS=(--needed) PACMAN_INSTALL_FLAGS=(--needed)
PACMAN_REMOVE_FLAGS=() 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." log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation."
else else
PACMAN_INSTALL_FLAGS+=(--noconfirm) PACMAN_INSTALL_FLAGS+=(--noconfirm)
PACMAN_REMOVE_FLAGS+=(--noconfirm) PACMAN_REMOVE_FLAGS+=(--noconfirm)
fi fi
local kernel_release host_package local kernel_release host_package
kernel_release=$(detect_kernel_release) kernel_release=$(detect_kernel_release)
log_info "Detected running kernel: ${kernel_release}" log_info "Detected running kernel: ${kernel_release}"
host_package=$(select_host_package "${kernel_release}") host_package=$(select_host_package "${kernel_release}")
log_info "Selected VirtualBox host package: ${host_package}" log_info "Selected VirtualBox host package: ${host_package}"
mapfile -t kernel_headers < <(collect_kernel_headers) 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." log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules."
fi fi
maybe_remove_conflicting_host_packages "${host_package}" maybe_remove_conflicting_host_packages "${host_package}"
install_packages "${host_package}" "${kernel_headers[@]}" install_packages "${host_package}" "${kernel_headers[@]}"
rebuild_virtualbox_modules "${host_package}" rebuild_virtualbox_modules "${host_package}"
reload_virtualbox_modules reload_virtualbox_modules
warn_if_secure_boot_enabled warn_if_secure_boot_enabled
remind_group_membership remind_group_membership
log_info "VirtualBox installation and driver setup complete." log_info "VirtualBox installation and driver setup complete."
} }
main "$@" main "$@"

View File

@ -3,40 +3,40 @@
# Script to disable NVIDIA GSP firmware and apply comprehensive NVIDIA fixes # Script to disable NVIDIA GSP firmware and apply comprehensive NVIDIA fixes
# This addresses GSP issues, mesh shaders, OpenGL problems, and other NVIDIA issues # This addresses GSP issues, mesh shaders, OpenGL problems, and other NVIDIA issues
set -e # Exit on any error set -e # Exit on any error
# Default to non-interactive mode # Default to non-interactive mode
INTERACTIVE_MODE=false INTERACTIVE_MODE=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--interactive) -i | --interactive)
INTERACTIVE_MODE=true INTERACTIVE_MODE=true
shift shift
;; ;;
-h|--help) -h | --help)
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
echo "Options:" echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)" echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
exit 0 exit 0
;; ;;
*) *)
echo "Unknown option: $1" echo "Unknown option: $1"
echo "Use -h or --help for usage information" echo "Use -h or --help for usage information"
exit 1 exit 1
;; ;;
esac esac
done done
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to modify system files." echo "This script requires sudo privileges to modify system files."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Check for sudo privileges after argument parsing # Check for sudo privileges after argument parsing
@ -47,15 +47,15 @@ echo "=================================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $USER" echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}" echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)" echo "Mode: Interactive (prompts enabled)"
else else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)" echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
fi fi
# Check if nvidia module is loaded # Check if nvidia module is loaded
if ! lsmod | grep -q nvidia; then if ! lsmod | grep -q nvidia; then
echo "Warning: NVIDIA module not currently loaded" echo "Warning: NVIDIA module not currently loaded"
fi fi
# Create modprobe configuration directory if it doesn't exist # Create modprobe configuration directory if it doesn't exist
@ -78,32 +78,32 @@ echo "✓ Configuration written to: $CONFIG_FILE"
# Function to backup file if it exists # Function to backup file if it exists
backup_file() { backup_file() {
local file="$1" local file="$1"
if [[ -f "$file" ]]; then if [[ -f $file ]]; then
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)" cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
echo "✓ Backed up $file" echo "✓ Backed up $file"
fi fi
} }
# Function to add or update xorg.conf for RenderAccel # Function to add or update xorg.conf for RenderAccel
configure_xorg() { configure_xorg() {
echo "" echo ""
echo "2. Configuring Xorg Settings..." echo "2. Configuring Xorg Settings..."
echo "===============================" echo "==============================="
XORG_CONF="/etc/X11/xorg.conf" XORG_CONF="/etc/X11/xorg.conf"
XORG_CONF_D="/etc/X11/xorg.conf.d" XORG_CONF_D="/etc/X11/xorg.conf.d"
NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf" NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf"
# Create xorg.conf.d directory if it doesn't exist # Create xorg.conf.d directory if it doesn't exist
mkdir -p "$XORG_CONF_D" mkdir -p "$XORG_CONF_D"
# Backup existing xorg.conf if it exists # Backup existing xorg.conf if it exists
backup_file "$XORG_CONF" backup_file "$XORG_CONF"
backup_file "$NVIDIA_CONF" backup_file "$NVIDIA_CONF"
# Create NVIDIA-specific configuration # Create NVIDIA-specific configuration
cat > "$NVIDIA_CONF" << EOF cat > "$NVIDIA_CONF" << EOF
# NVIDIA configuration with RenderAccel disabled # NVIDIA configuration with RenderAccel disabled
# Created by nvidia_troubleshoot.sh on $(date) # Created by nvidia_troubleshoot.sh on $(date)
Section "Device" Section "Device"
@ -113,102 +113,106 @@ Section "Device"
EndSection EndSection
EOF EOF
echo "✓ Created $NVIDIA_CONF with RenderAccel disabled" echo "✓ Created $NVIDIA_CONF with RenderAccel disabled"
} }
# Function to add GCC mismatch workaround # Function to add GCC mismatch workaround
configure_gcc_workaround() { configure_gcc_workaround() {
echo "" echo ""
echo "3. Configuring GCC Mismatch Workaround..." echo "3. Configuring GCC Mismatch Workaround..."
echo "==========================================" echo "=========================================="
PROFILE_FILE="/etc/profile" local PROFILE_FILE="/etc/profile"
backup_file "$PROFILE_FILE" local timestamp
timestamp=$(date)
backup_file "$PROFILE_FILE"
# Check if IGNORE_CC_MISMATCH is already set # Check if IGNORE_CC_MISMATCH is already set
if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then
echo "" >> "$PROFILE_FILE" {
echo "# NVIDIA GCC version mismatch workaround" >> "$PROFILE_FILE" printf '\n'
echo "# Added by nvidia_troubleshoot.sh on $(date)" >> "$PROFILE_FILE" printf '# NVIDIA GCC version mismatch workaround\n'
echo "export IGNORE_CC_MISMATCH=1" >> "$PROFILE_FILE" printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE" printf 'export IGNORE_CC_MISMATCH=1\n'
else } >> "$PROFILE_FILE"
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE" echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
fi else
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
fi
} }
# Function to install pyroveil for mesh shader issues # Function to install pyroveil for mesh shader issues
install_pyroveil() { install_pyroveil() {
echo "" echo ""
echo "4. Pyroveil Setup for Mesh Shader Issues..." echo "4. Pyroveil Setup for Mesh Shader Issues..."
echo "===========================================" echo "==========================================="
local user_home="/home/$SUDO_USER" local user_home="/home/$SUDO_USER"
local pyroveil_dir="$user_home/pyroveil" local pyroveil_dir="$user_home/pyroveil"
echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games" echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games"
echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems." echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems."
echo "" echo ""
local install_pyroveil=true 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 read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
echo echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then
install_pyroveil=false install_pyroveil=false
fi fi
else else
echo "Auto-installing Pyroveil (use --interactive to prompt)" echo "Auto-installing Pyroveil (use --interactive to prompt)"
fi
if [[ $install_pyroveil == "true" ]]; then
# Check for required dependencies
local missing_deps=()
for dep in git cmake ninja gcc; do
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo "Missing dependencies: ${missing_deps[*]}"
echo "Please install them first. On Arch Linux:"
echo "pacman -S base-devel git cmake ninja"
return 1
fi fi
if [[ "$install_pyroveil" == "true" ]]; then # Clone and build pyroveil as the original user
# Check for required dependencies echo "Installing Pyroveil to $pyroveil_dir..."
local missing_deps=()
for dep in git cmake ninja gcc; do if [[ -d $pyroveil_dir ]]; then
if ! command -v $dep &> /dev/null; then echo "Pyroveil directory already exists. Updating..."
missing_deps+=($dep) sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
fi else
done sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then sudo -u "$SUDO_USER" bash -c "
echo "Missing dependencies: ${missing_deps[*]}"
echo "Please install them first. On Arch Linux:"
echo "pacman -S base-devel git cmake ninja"
return 1
fi
# Clone and build pyroveil as the original user
echo "Installing Pyroveil to $pyroveil_dir..."
if [[ -d "$pyroveil_dir" ]]; then
echo "Pyroveil directory already exists. Updating..."
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
else
sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
fi
sudo -u "$SUDO_USER" bash -c "
cd '$pyroveil_dir' cd '$pyroveil_dir'
git submodule update --init git submodule update --init
cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local
ninja -C build install ninja -C build install
" "
echo "✓ Pyroveil installed successfully" echo "✓ Pyroveil installed successfully"
echo "" echo ""
echo "To use Pyroveil with games that have mesh shader issues:" echo "To use Pyroveil with games that have mesh shader issues:"
echo "1. For Final Fantasy VII Rebirth:" echo "1. For Final Fantasy VII Rebirth:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%" echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%"
echo "" echo ""
echo "2. For Steam games, add to launch options:" echo "2. For Steam games, add to launch options:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%" echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%"
echo "" echo ""
echo "Available configs in: $pyroveil_dir/hacks/" echo "Available configs in: $pyroveil_dir/hacks/"
# Create a helper script # Create a helper script
cat > "$user_home/run-with-pyroveil.sh" << EOF cat > "$user_home/run-with-pyroveil.sh" << EOF
#!/bin/bash #!/bin/bash
# Helper script to run games with Pyroveil # Helper script to run games with Pyroveil
# Usage: ./run-with-pyroveil.sh <config-name> <command> # Usage: ./run-with-pyroveil.sh <config-name> <command>
@ -234,87 +238,88 @@ echo "Config file: \$PYROVEIL_CONFIG"
exec "\$@" exec "\$@"
EOF EOF
chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh" chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh"
chmod +x "$user_home/run-with-pyroveil.sh" chmod +x "$user_home/run-with-pyroveil.sh"
echo "✓ Created helper script: $user_home/run-with-pyroveil.sh" echo "✓ Created helper script: $user_home/run-with-pyroveil.sh"
else else
echo "Skipping Pyroveil installation" echo "Skipping Pyroveil installation"
echo "Note: You can manually install it later for mesh shader issues" echo "Note: You can manually install it later for mesh shader issues"
fi fi
} }
# Function to check for kernel parameter modifications # Function to check for kernel parameter modifications
suggest_kernel_params() { suggest_kernel_params() {
echo "" echo ""
echo "5. Kernel Parameter Recommendations..." echo "5. Kernel Parameter Recommendations..."
echo "=====================================" echo "====================================="
echo "NVIDIA Driver Issues and Recommended Kernel Parameters:" echo "NVIDIA Driver Issues and Recommended Kernel Parameters:"
echo "" echo ""
echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors" echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors"
echo " (especially with nvidia-96xx drivers):" echo " (especially with nvidia-96xx drivers):"
echo " → Add 'nopat' to kernel parameters" echo " → Add 'nopat' to kernel parameters"
echo "" echo ""
echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:" echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:"
echo " → Consider disabling micro-op cache in BIOS settings" echo " → Consider disabling micro-op cache in BIOS settings"
echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs" echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs"
echo " → Helps with severe graphical glitches in Xwayland applications" echo " → Helps with severe graphical glitches in Xwayland applications"
echo " → Note: Disabling micro-op cache reduces CPU performance" echo " → Note: Disabling micro-op cache reduces CPU performance"
echo "" echo ""
echo "To add kernel parameters:" echo "To add kernel parameters:"
echo "1. Edit /etc/default/grub" echo "1. Edit /etc/default/grub"
echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT" echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT"
echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg" echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg"
echo "4. Reboot" echo "4. Reboot"
echo "" echo ""
echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:" echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:"
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"' echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"'
# Check current CPU for micro-op cache relevance # Check current CPU for micro-op cache relevance
echo "" echo ""
echo "CPU Information (for micro-op cache consideration):" echo "CPU Information (for micro-op cache consideration):"
if command -v lscpu &> /dev/null; then if command -v lscpu &> /dev/null; then
local cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs) local cpu_info
echo "Current CPU: $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 if echo "$cpu_info" | grep -qi "intel"; then
echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache" echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache"
elif echo "$cpu_info" | grep -qi "amd"; then elif echo "$cpu_info" | grep -qi "amd"; then
echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache" echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache"
fi
fi fi
fi
} }
# Function to suggest desktop environment settings # Function to suggest desktop environment settings
suggest_desktop_settings() { suggest_desktop_settings() {
echo "" echo ""
echo "6. Desktop Environment Recommendations..." echo "6. Desktop Environment Recommendations..."
echo "========================================" echo "========================================"
echo "For fullscreen application freezing/crashing issues:" echo "For fullscreen application freezing/crashing issues:"
echo "" echo ""
echo "Enable Display Compositing and Direct fullscreen rendering:" echo "Enable Display Compositing and Direct fullscreen rendering:"
echo "" echo ""
echo "• KDE Plasma:" echo "• KDE Plasma:"
echo " System Settings → Display and Monitor → Compositor" echo " System Settings → Display and Monitor → Compositor"
echo " → Enable compositor + Enable direct rendering for fullscreen windows" echo " → Enable compositor + Enable direct rendering for fullscreen windows"
echo "" echo ""
echo "• GNOME:" echo "• GNOME:"
echo " Use Extensions or dconf-editor to enable compositing features" echo " Use Extensions or dconf-editor to enable compositing features"
echo "" echo ""
echo "• XFCE:" echo "• XFCE:"
echo " Settings → Window Manager Tweaks → Compositor" echo " Settings → Window Manager Tweaks → Compositor"
echo " → Enable display compositing" echo " → Enable display compositing"
echo "" echo ""
echo "• Cinnamon:" echo "• Cinnamon:"
echo " System Settings → Effects → Enable desktop effects" echo " System Settings → Effects → Enable desktop effects"
# Detect current desktop environment # Detect current desktop environment
if [[ -n "$XDG_CURRENT_DESKTOP" ]]; then if [[ -n $XDG_CURRENT_DESKTOP ]]; then
echo "" echo ""
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP" echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
fi fi
} }
# Apply all configurations # Apply all configurations
@ -327,13 +332,13 @@ echo ""
echo "7. Regenerating Initramfs..." echo "7. Regenerating Initramfs..."
echo "============================" echo "============================"
if command -v mkinitcpio &> /dev/null; then if command -v mkinitcpio &> /dev/null; then
mkinitcpio -P mkinitcpio -P
echo "✓ Initramfs regenerated with mkinitcpio" echo "✓ Initramfs regenerated with mkinitcpio"
elif command -v dracut &> /dev/null; then elif command -v dracut &> /dev/null; then
dracut --force dracut --force
echo "✓ Initramfs regenerated with dracut" echo "✓ Initramfs regenerated with dracut"
else else
echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs." echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs."
fi fi
# Display all recommendations # Display all recommendations
@ -349,7 +354,7 @@ echo "✓ GSP firmware disabled"
echo "✓ RenderAccel disabled in Xorg configuration" echo "✓ RenderAccel disabled in Xorg configuration"
echo "✓ GCC version mismatch workaround added" echo "✓ GCC version mismatch workaround added"
if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then
echo "✓ Pyroveil installed for mesh shader issues" echo "✓ Pyroveil installed for mesh shader issues"
fi fi
echo "✓ Initramfs regenerated" echo "✓ Initramfs regenerated"
echo "" echo ""

View File

@ -25,24 +25,20 @@ INSTALL_ONLY="false"
LIST_ONLY="false" LIST_ONLY="false"
VERBOSE="false" VERBOSE="false"
log() {
printf '%s\n' "$*"
}
log_info() { log_info() {
printf '\033[1;34m[INFO]\033[0m %s\n' "$*" printf '\033[1;34m[INFO]\033[0m %s\n' "$*"
} }
log_warn() { log_warn() {
printf '\033[1;33m[WARN]\033[0m %s\n' "$*" printf '\033[1;33m[WARN]\033[0m %s\n' "$*"
} }
log_error() { log_error() {
printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2
} }
usage() { usage() {
cat <<EOF cat << EOF
Usage: $(basename "$0") [options] Usage: $(basename "$0") [options]
Options: Options:
@ -61,341 +57,363 @@ EOF
} }
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--path) --path)
ROOT_DIR="$2"; shift 2 ;; ROOT_DIR="$2"
--skip-install) shift 2
SKIP_INSTALL="true"; shift ;; ;;
--install-only) --skip-install)
INSTALL_ONLY="true"; shift ;; SKIP_INSTALL="true"
--list-only) shift
LIST_ONLY="true"; shift ;; ;;
--verbose) --install-only)
VERBOSE="true"; shift ;; INSTALL_ONLY="true"
-h|--help) shift
usage; exit 0 ;; ;;
*) --list-only)
log_error "Unknown argument: $1"; usage; exit 2 ;; LIST_ONLY="true"
esac shift
;;
--verbose)
VERBOSE="true"
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown argument: $1"
usage
exit 2
;;
esac
done done
if [[ ! -d "$ROOT_DIR" ]]; then if [[ ! -d $ROOT_DIR ]]; then
log_error "Path not found: $ROOT_DIR" log_error "Path not found: $ROOT_DIR"
exit 2 exit 2
fi fi
is_cmd() { command -v "$1" >/dev/null 2>&1; } is_cmd() { command -v "$1" > /dev/null 2>&1; }
is_arch() { is_cmd pacman; } is_arch() { is_cmd pacman; }
have_aur_helper() { is_cmd yay || is_cmd paru; } have_aur_helper() { is_cmd yay || is_cmd paru; }
install_if_missing() { install_if_missing() {
local pkg cmd local pkg cmd
pkg="$1"; cmd="$2" pkg="$1"
if is_cmd "$cmd"; then cmd="$2"
[[ "$VERBOSE" == "true" ]] && log_info "Found $cmd" if is_cmd "$cmd"; then
return 0 [[ $VERBOSE == "true" ]] && log_info "Found $cmd"
fi return 0
fi
if [[ "$SKIP_INSTALL" == "true" ]]; then if [[ $SKIP_INSTALL == "true" ]]; then
log_warn "Skipping install of $pkg ($cmd not found)" log_warn "Skipping install of $pkg ($cmd not found)"
return 1 return 1
fi fi
if is_arch; then if is_arch; then
log_info "Installing $pkg via pacman..." log_info "Installing $pkg via pacman..."
if ! sudo pacman -S --needed --noconfirm "$pkg"; then if ! sudo pacman -S --needed --noconfirm "$pkg"; then
log_warn "Failed to install $pkg via pacman." log_warn "Failed to install $pkg via pacman."
return 1 return 1
fi fi
return 0 return 0
else else
log_warn "Non-Arch system detected. Please install '$pkg' manually." log_warn "Non-Arch system detected. Please install '$pkg' manually."
return 1 return 1
fi fi
} }
install_linters() { install_linters() {
local ok=0 local ok=0
# Core linters # Core linters
install_if_missing shellcheck shellcheck || ok=1 install_if_missing shellcheck shellcheck || ok=1
install_if_missing shfmt shfmt || ok=1 install_if_missing shfmt shfmt || ok=1
# Optional linters (best-effort) # Optional linters (best-effort)
# checkbashisms may be in repos or AUR; try pacman first, then AUR helper # checkbashisms may be in repos or AUR; try pacman first, then AUR helper
if ! is_cmd checkbashisms; then if ! is_cmd checkbashisms; then
if is_arch; 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 if have_aur_helper; then
log_info "Installing checkbashisms from AUR (requires yay/paru)..." log_info "Installing checkbashisms from AUR (requires yay/paru)..."
if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi
if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi
else else
log_warn "checkbashisms not installed (no AUR helper)." log_warn "checkbashisms not installed (no AUR helper)."
fi fi
fi fi
fi fi
fi fi
# bashate (python-based), typically available as python-bashate in AUR # bashate (python-based), typically available as python-bashate in AUR
if ! is_cmd bashate; then if ! is_cmd bashate; then
if is_arch && have_aur_helper; then if is_arch && have_aur_helper; then
log_info "Installing bashate from AUR (requires yay/paru)..." log_info "Installing bashate from AUR (requires yay/paru)..."
if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi
if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi
else else
# Try pip if user has it and wants to # Try pip if user has it and wants to
if is_cmd pipx; then if is_cmd pipx; then
log_info "Installing bashate via pipx..." log_info "Installing bashate via pipx..."
pipx install bashate || true pipx install bashate || true
elif is_cmd pip3; then elif is_cmd pip3; then
log_info "Installing bashate via pip (user)..." log_info "Installing bashate via pip (user)..."
pip3 install --user bashate || true pip3 install --user bashate || true
else else
log_warn "bashate not installed (no AUR helper or pip available)." log_warn "bashate not installed (no AUR helper or pip available)."
fi fi
fi fi
fi fi
return "$ok" return "$ok"
} }
TMPDIR=$(mktemp -d) TMPDIR=$(mktemp -d)
cleanup() { rm -rf "$TMPDIR"; } trap 'rm -rf "${TMPDIR:-}"' EXIT
trap cleanup EXIT
ABS_FILES_Z="$TMPDIR/files_abs.zlist" ABS_FILES_Z="$TMPDIR/files_abs.zlist"
REL_FILES_Z="$TMPDIR/files_rel.zlist" REL_FILES_Z="$TMPDIR/files_rel.zlist"
discover_shell_files() { discover_shell_files() {
local base="$1" local base="$1"
local -a all local -a all
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 -z)
while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z) while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z)
else else
while IFS= read -r -d '' f; do while IFS= read -r -d '' f; do
# trim leading ./ to keep consistent style with git paths # trim leading ./ to keep consistent style with git paths
f="${f#./}" f="${f#./}"
f="${f#${base}/}" f="${f#"${base}"/}"
all+=("$f") all+=("$f")
done < <(find "$base" -type f -print0) done < <(find "$base" -type f -print0)
fi fi
local -a shells local -a shells
shells=() shells=()
for rel in "${all[@]}"; do for rel in "${all[@]}"; do
# skip binary-ish or huge files quickly by extension heuristic # skip binary-ish or huge files quickly by extension heuristic
case "$rel" in case "$rel" in
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.pdf|*.svg|*.zip|*.tar|*.gz|*.xz|*.7z|*.so|*.o|*.bin) *.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin)
continue ;; continue
esac ;;
esac
local abs="$base/$rel" 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") shells+=("$rel")
continue continue
fi fi
# Check shebang # Check shebang
local first local first
first=$(head -n 1 -- "$abs" 2>/dev/null || true) first=$(head -n 1 -- "$abs" 2> /dev/null || true)
if [[ "$first" =~ ^#! && "$first" =~ (ba|z|d|k)?sh ]]; then if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
shells+=("$rel") shells+=("$rel")
continue continue
fi fi
# Also catch executable files with shell shebang even without extension # Also catch executable files with shell shebang even without extension
if [[ -x "$abs" ]]; then if [[ -x $abs ]]; then
if [[ "$first" =~ ^#! && "$first" =~ (ba|z|d|k)?sh ]]; then if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
shells+=("$rel") shells+=("$rel")
fi fi
fi fi
done done
# write lists # write lists
: >"$REL_FILES_Z" : > "$REL_FILES_Z"
: >"$ABS_FILES_Z" : > "$ABS_FILES_Z"
for rel in "${shells[@]}"; do for rel in "${shells[@]}"; do
printf '%s\0' "$rel" >> "$REL_FILES_Z" printf '%s\0' "$rel" >> "$REL_FILES_Z"
printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z" printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z"
done done
} }
print_file_list() { print_file_list() {
local count local count
count=$(tr -cd '\0' < "$REL_FILES_Z" | wc -c) count=$(tr -cd '\0' < "$REL_FILES_Z" | wc -c)
log_info "Discovered $count shell file(s) under $ROOT_DIR" 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/^/ - /' tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /'
fi fi
} }
run_linters() { run_linters() {
local issues=0 local issues=0
local count local count
count=$(tr -cd '\0' < "$ABS_FILES_Z" | wc -c) 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." log_warn "No shell files found to lint."
return 0 return 0
fi fi
mapfile -d '' -t FILES < "$ABS_FILES_Z" mapfile -d '' -t FILES < "$ABS_FILES_Z"
log_info "Running shellcheck..." log_info "Running shellcheck..."
local sc_out="$TMPDIR/shellcheck.txt" local sc_out="$TMPDIR/shellcheck.txt"
if is_cmd shellcheck; then if is_cmd shellcheck; then
if ! shellcheck -x -S style "${FILES[@]}" >"$sc_out" 2>&1; then if ! shellcheck -x -S style "${FILES[@]}" > "$sc_out" 2>&1; then
issues=$((issues+1)) issues=$((issues + 1))
fi fi
else else
log_warn "shellcheck not found; skipping" log_warn "shellcheck not found; skipping"
fi fi
log_info "Running shfmt (diff mode)..." log_info "Running shfmt (diff mode)..."
local shfmt_out="$TMPDIR/shfmt.diff" local shfmt_out="$TMPDIR/shfmt.diff"
if is_cmd shfmt; then 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 # shfmt returns non-zero when diff exists
issues=$((issues+1)) issues=$((issues + 1))
fi fi
else else
log_warn "shfmt not found; skipping" log_warn "shfmt not found; skipping"
fi fi
log_info "Running checkbashisms (optional)..." log_info "Running checkbashisms (optional)..."
local cbi_out="$TMPDIR/checkbashisms.txt" local cbi_out="$TMPDIR/checkbashisms.txt"
if is_cmd checkbashisms; then local cbi_status=0
# checkbashisms exits 0 if OK, 1 if issues if is_cmd checkbashisms; then
if ! checkbashisms "${FILES[@]}" >"$cbi_out" 2>&1; then # checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings
issues=$((issues+1)) checkbashisms "${FILES[@]}" > "$cbi_out" 2>&1
fi cbi_status=$?
else if [[ $cbi_status -eq 1 ]]; then
log_warn "checkbashisms not found; skipping" issues=$((issues + 1))
fi elif [[ $cbi_status -ne 0 ]]; then
log_warn "checkbashisms exited with status $cbi_status (treated as warning)"
fi
else
log_warn "checkbashisms not found; skipping"
fi
log_info "Running bash/zsh/sh syntax checks (-n)..." log_info "Running bash/zsh/sh syntax checks (-n)..."
local bash_out="$TMPDIR/bash_syntax.txt" local bash_out="$TMPDIR/bash_syntax.txt"
local zsh_out="$TMPDIR/zsh_syntax.txt" local zsh_out="$TMPDIR/zsh_syntax.txt"
local sh_out="$TMPDIR/sh_syntax.txt" local sh_out="$TMPDIR/sh_syntax.txt"
# Partition files by shebang for better accuracy # Partition files by shebang for better accuracy
local -a BASH_FILES ZSH_FILES SH_FILES local -a BASH_FILES ZSH_FILES SH_FILES
BASH_FILES=(); ZSH_FILES=(); SH_FILES=() BASH_FILES=()
for f in "${FILES[@]}"; do ZSH_FILES=()
local first SH_FILES=()
first=$(head -n 1 -- "$f" 2>/dev/null || true) for f in "${FILES[@]}"; do
if [[ "$first" =~ bash ]]; then local first
BASH_FILES+=("$f") first=$(head -n 1 -- "$f" 2> /dev/null || true)
elif [[ "$first" =~ zsh ]]; then if [[ $first =~ bash ]]; then
ZSH_FILES+=("$f") BASH_FILES+=("$f")
else elif [[ $first =~ zsh ]]; then
SH_FILES+=("$f") ZSH_FILES+=("$f")
fi else
done SH_FILES+=("$f")
fi
done
if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then
if ! bash -n "${BASH_FILES[@]}" 2>"$bash_out"; then if ! bash -n "${BASH_FILES[@]}" 2> "$bash_out"; then
issues=$((issues+1)) issues=$((issues + 1))
fi fi
fi fi
if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then
if ! zsh -n "${ZSH_FILES[@]}" 2>"$zsh_out"; then if ! zsh -n "${ZSH_FILES[@]}" 2> "$zsh_out"; then
issues=$((issues+1)) issues=$((issues + 1))
fi fi
fi fi
# prefer dash if present for /bin/sh style # prefer dash if present for /bin/sh style
if [[ ${#SH_FILES[@]} -gt 0 ]]; then if [[ ${#SH_FILES[@]} -gt 0 ]]; then
if is_cmd dash; then if is_cmd dash; then
if ! dash -n "${SH_FILES[@]}" 2>"$sh_out"; then if ! dash -n "${SH_FILES[@]}" 2> "$sh_out"; then
issues=$((issues+1)) issues=$((issues + 1))
fi fi
elif is_cmd sh; then elif is_cmd sh; then
if ! sh -n "${SH_FILES[@]}" 2>"$sh_out"; then if ! sh -n "${SH_FILES[@]}" 2> "$sh_out"; then
issues=$((issues+1)) issues=$((issues + 1))
fi fi
fi fi
fi fi
echo echo
log_info "========== Shell Lint Report ==========" log_info "========== Shell Lint Report =========="
if [[ -s "$sc_out" ]]; then if [[ -s $sc_out ]]; then
printf '\n\033[1m-- shellcheck --\033[0m\n' printf '\n\033[1m-- shellcheck --\033[0m\n'
cat "$sc_out" cat "$sc_out"
else else
printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n' printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n'
fi fi
if [[ -s "$shfmt_out" ]]; then if [[ -s $shfmt_out ]]; then
printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n' printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n'
cat "$shfmt_out" cat "$shfmt_out"
else else
printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n' printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n'
fi fi
if [[ -s "$cbi_out" ]]; then if [[ -s $cbi_out ]]; then
printf '\n\033[1m-- checkbashisms --\033[0m\n' printf '\n\033[1m-- checkbashisms --\033[0m\n'
cat "$cbi_out" cat "$cbi_out"
else else
printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n' printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n'
fi fi
if [[ -s "$bash_out" ]]; then if [[ -s $bash_out ]]; then
printf '\n\033[1m-- bash -n (syntax) --\033[0m\n' printf '\n\033[1m-- bash -n (syntax) --\033[0m\n'
cat "$bash_out" cat "$bash_out"
else else
printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n' printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n'
fi fi
if [[ -s "$zsh_out" ]]; then if [[ -s $zsh_out ]]; then
printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n' printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n'
cat "$zsh_out" cat "$zsh_out"
else else
printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n' printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n'
fi fi
if [[ -s "$sh_out" ]]; then if [[ -s $sh_out ]]; then
printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n' printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n'
cat "$sh_out" cat "$sh_out"
else else
printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n' printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n'
fi fi
echo echo
if [[ $issues -gt 0 ]]; then if [[ $issues -gt 0 ]]; then
log_error "Linting completed with $issues tool(s) reporting issues." log_error "Linting completed with $issues tool(s) reporting issues."
return 1 return 1
else else
log_info "All checks passed." log_info "All checks passed."
return 0 return 0
fi fi
} }
# Main # Main
if [[ "$INSTALL_ONLY" == "true" ]]; then if [[ $INSTALL_ONLY == "true" ]]; then
install_linters install_linters
exit $? exit $?
fi fi
# Only attempt installs if not list-only # Only attempt installs if not list-only
if [[ "$LIST_ONLY" != "true" ]]; then if [[ $LIST_ONLY != "true" ]]; then
install_linters || true install_linters || true
fi fi
discover_shell_files "$ROOT_DIR" discover_shell_files "$ROOT_DIR"
print_file_list print_file_list
if [[ "$LIST_ONLY" == "true" ]]; then if [[ $LIST_ONLY == "true" ]]; then
exit 0 exit 0
fi fi
run_linters run_linters
exit $? exit $?

View File

@ -3,40 +3,40 @@
# Executes every hour and on system startup # Executes every hour and on system startup
# Handles sudo privileges automatically # Handles sudo privileges automatically
set -e # Exit on any error set -e # Exit on any error
# Default to non-interactive mode # Default to non-interactive mode
INTERACTIVE_MODE=false INTERACTIVE_MODE=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--interactive) -i | --interactive)
INTERACTIVE_MODE=true INTERACTIVE_MODE=true
shift shift
;; ;;
-h|--help) -h | --help)
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
echo "Options:" echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)" echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
exit 0 exit 0
;; ;;
*) *)
echo "Unknown option: $1" echo "Unknown option: $1"
echo "Use -h or --help for usage information" echo "Use -h or --help for usage information"
exit 1 exit 1
;; ;;
esac esac
done done
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to create systemd services and timers." echo "This script requires sudo privileges to create systemd services and timers."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Check for sudo privileges after argument parsing # Check for sudo privileges after argument parsing
@ -47,10 +47,10 @@ echo "==================================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $USER" echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}" echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)" echo "Mode: Interactive (prompts enabled)"
else else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)" echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
fi fi
# Get the directory where this script is located # Get the directory where this script is located
@ -86,231 +86,231 @@ TEMPLATE_LOGROTATE="$LOGROTATE_TEMPLATES/periodic-system-maintenance"
# Function to verify required files exist # Function to verify required files exist
verify_files() { verify_files() {
echo "" echo ""
echo "1. Verifying Required Files..." echo "1. Verifying Required Files..."
echo "==============================" echo "=============================="
local missing_files=() local missing_files=()
if [[ ! -f "$PACMAN_WRAPPER_SCRIPT" ]]; then if [[ ! -f $PACMAN_WRAPPER_SCRIPT ]]; then
missing_files+=("$PACMAN_WRAPPER_SCRIPT") missing_files+=("$PACMAN_WRAPPER_SCRIPT")
fi fi
if [[ ! -f "$PACMAN_WRAPPER_INSTALL" ]]; then if [[ ! -f $PACMAN_WRAPPER_INSTALL ]]; then
missing_files+=("$PACMAN_WRAPPER_INSTALL") missing_files+=("$PACMAN_WRAPPER_INSTALL")
fi fi
if [[ ! -f "$HOSTS_INSTALL_SCRIPT" ]]; then if [[ ! -f $HOSTS_INSTALL_SCRIPT ]]; then
missing_files+=("$HOSTS_INSTALL_SCRIPT") missing_files+=("$HOSTS_INSTALL_SCRIPT")
fi fi
# Check template files as well # Check template files as well
for tmpl in \ for tmpl in \
"$TEMPLATE_MAINT_SCRIPT" \ "$TEMPLATE_MAINT_SCRIPT" \
"$TEMPLATE_HOSTS_MONITOR" \ "$TEMPLATE_HOSTS_MONITOR" \
"$TEMPLATE_BROWSER_WRAPPER" \ "$TEMPLATE_BROWSER_WRAPPER" \
"$TEMPLATE_SVC_MAINT" \ "$TEMPLATE_SVC_MAINT" \
"$TEMPLATE_TIMER" \ "$TEMPLATE_TIMER" \
"$TEMPLATE_STARTUP" \ "$TEMPLATE_STARTUP" \
"$TEMPLATE_HOSTS_SVC" \ "$TEMPLATE_HOSTS_SVC" \
"$TEMPLATE_LOGROTATE"; do "$TEMPLATE_LOGROTATE"; do
if [[ ! -f "$tmpl" ]]; then if [[ ! -f $tmpl ]]; then
missing_files+=("$tmpl") missing_files+=("$tmpl")
fi
done
if [[ ${#missing_files[@]} -gt 0 ]]; then
echo "Error: The following required files are missing:"
for file in "${missing_files[@]}"; do
echo " - $file"
done
exit 1
fi fi
done
echo "✓ All required files found" if [[ ${#missing_files[@]} -gt 0 ]]; then
echo "Error: The following required files are missing:"
for file in "${missing_files[@]}"; do
echo " - $file"
done
exit 1
fi
echo "✓ All required files found"
} }
# Function to create the combined execution script # Function to create the combined execution script
create_execution_script() { create_execution_script() {
echo "" echo ""
echo "2. Creating Combined Execution Script..." echo "2. Creating Combined Execution Script..."
echo "=======================================" echo "======================================="
local exec_script="/usr/local/bin/periodic-system-maintenance.sh" local exec_script="/usr/local/bin/periodic-system-maintenance.sh"
# Install from template with path substitutions # Install from template with path substitutions
sed \ sed \
-e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \ -e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \
-e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_MAINT_SCRIPT" > "$exec_script" "$TEMPLATE_MAINT_SCRIPT" > "$exec_script"
chmod +x "$exec_script" chmod +x "$exec_script"
echo "✓ Installed execution script from template: $exec_script" echo "✓ Installed execution script from template: $exec_script"
} }
# Function to create systemd service # Function to create systemd service
create_systemd_service() { create_systemd_service() {
echo "" echo ""
echo "3. Creating Systemd Service..." echo "3. Creating Systemd Service..."
echo "=============================" echo "============================="
local service_file="/etc/systemd/system/periodic-system-maintenance.service" local service_file="/etc/systemd/system/periodic-system-maintenance.service"
install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file" install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file"
echo "✓ Installed systemd service from template: $service_file" echo "✓ Installed systemd service from template: $service_file"
} }
# Function to create systemd timer for hourly execution # Function to create systemd timer for hourly execution
create_systemd_timer() { create_systemd_timer() {
echo "" echo ""
echo "4. Creating Systemd Timer..." echo "4. Creating Systemd Timer..."
echo "============================" echo "============================"
local timer_file="/etc/systemd/system/periodic-system-maintenance.timer" local timer_file="/etc/systemd/system/periodic-system-maintenance.timer"
install -m 0644 "$TEMPLATE_TIMER" "$timer_file" install -m 0644 "$TEMPLATE_TIMER" "$timer_file"
echo "✓ Installed systemd timer from template: $timer_file" echo "✓ Installed systemd timer from template: $timer_file"
} }
# Function to create startup service (additional to timer) # Function to create startup service (additional to timer)
create_startup_service() { create_startup_service() {
echo "" echo ""
echo "5. Creating Startup Service..." echo "5. Creating Startup Service..."
echo "==============================" echo "=============================="
local startup_service="/etc/systemd/system/periodic-system-startup.service" local startup_service="/etc/systemd/system/periodic-system-startup.service"
install -m 0644 "$TEMPLATE_STARTUP" "$startup_service" install -m 0644 "$TEMPLATE_STARTUP" "$startup_service"
echo "✓ Installed startup service from template: $startup_service" echo "✓ Installed startup service from template: $startup_service"
} }
# Function to create hosts file monitor service # Function to create hosts file monitor service
create_hosts_monitor_service() { create_hosts_monitor_service() {
echo "" echo ""
echo "6. Creating Hosts File Monitor Service..." echo "6. Creating Hosts File Monitor Service..."
echo "========================================" echo "========================================"
local monitor_script="/usr/local/bin/hosts-file-monitor.sh" local monitor_script="/usr/local/bin/hosts-file-monitor.sh"
local monitor_service="/etc/systemd/system/hosts-file-monitor.service" local monitor_service="/etc/systemd/system/hosts-file-monitor.service"
# Install the monitor script from template with substitution # Install the monitor script from template with substitution
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_HOSTS_MONITOR" > "$monitor_script" "$TEMPLATE_HOSTS_MONITOR" > "$monitor_script"
chmod +x "$monitor_script" chmod +x "$monitor_script"
echo "✓ Installed hosts monitor script from template: $monitor_script" echo "✓ Installed hosts monitor script from template: $monitor_script"
# Install the systemd service from template # Install the systemd service from template
install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service" install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service"
echo "✓ Installed hosts monitor service from template: $monitor_service" echo "✓ Installed hosts monitor service from template: $monitor_service"
} }
# Function to install browser pre-exec wrapper and wire common browser names # Function to install browser pre-exec wrapper and wire common browser names
install_browser_preexec_wrapper() { install_browser_preexec_wrapper() {
echo "" echo ""
echo "6.1 Installing Browser Pre-Exec Wrapper..." echo "6.1 Installing Browser Pre-Exec Wrapper..."
echo "=========================================" echo "========================================="
local wrapper="/usr/local/bin/browser-preexec-wrapper" local wrapper="/usr/local/bin/browser-preexec-wrapper"
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \ sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_BROWSER_WRAPPER" > "$wrapper" "$TEMPLATE_BROWSER_WRAPPER" > "$wrapper"
chmod +x "$wrapper" chmod +x "$wrapper"
echo "✓ Installed wrapper: $wrapper" echo "✓ Installed wrapper: $wrapper"
# Allow passwordless execution of hosts installer for root-only actions # Allow passwordless execution of hosts installer for root-only actions
local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd" 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" echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file"
chmod 440 "$sudoers_file" chmod 440 "$sudoers_file"
# Validate syntax # 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" echo "✓ Sudoers drop-in created: $sudoers_file"
else else
echo "visudo not found; skipping sudoers drop-in" echo "visudo not found; skipping sudoers drop-in"
fi fi
# Create symlinks for common browser commands to the wrapper in /usr/local/bin # Create symlinks for common browser commands to the wrapper in /usr/local/bin
# This takes precedence over /usr/bin in PATH on most systems. # 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 for b in "${browsers[@]}"; do
local link="/usr/local/bin/$b" local link="/usr/local/bin/$b"
ln -sf "$wrapper" "$link" ln -sf "$wrapper" "$link"
done done
echo "✓ Symlinked wrapper for common browsers in /usr/local/bin" echo "✓ Symlinked wrapper for common browsers in /usr/local/bin"
} }
# Function to enable and start services # Function to enable and start services
enable_services() { enable_services() {
echo "" echo ""
echo "7. Enabling Services and Timer..." echo "7. Enabling Services and Timer..."
echo "=================================" echo "================================="
# Reload systemd daemon # Reload systemd daemon
systemctl daemon-reload systemctl daemon-reload
echo "✓ Systemd daemon reloaded" echo "✓ Systemd daemon reloaded"
# Enable and start the timer # Enable and start the timer
systemctl enable periodic-system-maintenance.timer systemctl enable periodic-system-maintenance.timer
systemctl start periodic-system-maintenance.timer systemctl start periodic-system-maintenance.timer
echo "✓ Timer enabled and started" echo "✓ Timer enabled and started"
# Enable startup service (but don't start it now) # Enable startup service (but don't start it now)
systemctl enable periodic-system-startup.service systemctl enable periodic-system-startup.service
echo "✓ Startup service enabled" echo "✓ Startup service enabled"
# Enable hosts file monitor service # Enable hosts file monitor service
systemctl enable hosts-file-monitor.service systemctl enable hosts-file-monitor.service
systemctl start hosts-file-monitor.service systemctl start hosts-file-monitor.service
echo "✓ Hosts file monitor service enabled and started" echo "✓ Hosts file monitor service enabled and started"
# Show timer status # Show timer status
echo "" echo ""
echo "Timer Status:" echo "Timer Status:"
systemctl status periodic-system-maintenance.timer --no-pager -l systemctl status periodic-system-maintenance.timer --no-pager -l
echo "" echo ""
echo "Hosts Monitor Status:" echo "Hosts Monitor Status:"
systemctl status hosts-file-monitor.service --no-pager -l systemctl status hosts-file-monitor.service --no-pager -l
echo "" echo ""
echo "Next scheduled runs:" echo "Next scheduled runs:"
systemctl list-timers periodic-system-maintenance.timer --no-pager systemctl list-timers periodic-system-maintenance.timer --no-pager
} }
# Function to create log rotation configuration # Function to create log rotation configuration
create_log_rotation() { create_log_rotation() {
echo "" echo ""
echo "8. Setting up Log Rotation..." echo "8. Setting up Log Rotation..."
echo "=============================" echo "============================="
local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance" local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance"
install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf" install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf"
echo "✓ Installed log rotation configuration from template: $logrotate_conf" echo "✓ Installed log rotation configuration from template: $logrotate_conf"
} }
# Function to run initial execution # Function to run initial execution
run_initial_execution() { run_initial_execution() {
echo "" echo ""
echo "9. Running Initial Execution..." echo "9. Running Initial Execution..."
echo "===============================" echo "==============================="
local run_initial=true 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?" echo "Would you like to run the system maintenance now to test the setup?"
read -p "Run initial execution? (y/N): " -n 1 -r read -p "Run initial execution? (y/N): " -n 1 -r
echo echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then
run_initial=false run_initial=false
fi
else
echo "Auto-running initial execution to test the setup (use --interactive to prompt)"
fi fi
else
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..." echo "Running initial system maintenance..."
/usr/local/bin/periodic-system-maintenance.sh /usr/local/bin/periodic-system-maintenance.sh
echo "✓ Initial execution completed" echo "✓ Initial execution completed"
else else
echo "Skipping initial execution" echo "Skipping initial execution"
fi fi
} }
# Main execution # Main execution

View File

@ -2,40 +2,40 @@
# Script to set up automatic Thorium browser launch with Fitatu website on startup # Script to set up automatic Thorium browser launch with Fitatu website on startup
# Opens https://www.fitatu.com/ in Thorium browser every time the system boots # Opens https://www.fitatu.com/ in Thorium browser every time the system boots
set -e # Exit on any error set -e # Exit on any error
# Default to non-interactive mode # Default to non-interactive mode
INTERACTIVE_MODE=false INTERACTIVE_MODE=false
# Parse command line arguments # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
-i|--interactive) -i | --interactive)
INTERACTIVE_MODE=true INTERACTIVE_MODE=true
shift shift
;; ;;
-h|--help) -h | --help)
echo "Usage: $0 [OPTIONS]" echo "Usage: $0 [OPTIONS]"
echo "Options:" echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)" echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message" echo " -h, --help Show this help message"
exit 0 exit 0
;; ;;
*) *)
echo "Unknown option: $1" echo "Unknown option: $1"
echo "Use -h or --help for usage information" echo "Use -h or --help for usage information"
exit 1 exit 1
;; ;;
esac esac
done done
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to create systemd services." echo "This script requires sudo privileges to create systemd services."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Check for sudo privileges after argument parsing # Check for sudo privileges after argument parsing
@ -46,10 +46,10 @@ echo "=================================="
echo "Current Date: $(date)" echo "Current Date: $(date)"
echo "User: $USER" echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}" echo "Original user: ${SUDO_USER:-$USER}"
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)" echo "Mode: Interactive (prompts enabled)"
else else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)" echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
fi fi
# Target URL # Target URL
@ -64,72 +64,72 @@ echo "User home: $USER_HOME"
# Function to check if Thorium browser is installed # Function to check if Thorium browser is installed
check_thorium_browser() { check_thorium_browser() {
echo "" echo ""
echo "1. Checking Thorium Browser Installation..." echo "1. Checking Thorium Browser Installation..."
echo "==========================================" echo "=========================================="
if ! command -v "$BROWSER_COMMAND" &> /dev/null; then if ! command -v "$BROWSER_COMMAND" &> /dev/null; then
echo "Warning: Thorium browser not found in PATH" echo "Warning: Thorium browser not found in PATH"
echo "Checking alternative locations..." echo "Checking alternative locations..."
# Check common installation paths # Check common installation paths
local alt_paths=( local alt_paths=(
"/opt/thorium/thorium" "/opt/thorium/thorium"
"/usr/bin/thorium" "/usr/bin/thorium"
"/usr/local/bin/thorium" "/usr/local/bin/thorium"
"/opt/thorium-browser/thorium-browser" "/opt/thorium-browser/thorium-browser"
"${USER_HOME}/.local/bin/thorium-browser" "${USER_HOME}/.local/bin/thorium-browser"
) )
local found=false local found=false
for path in "${alt_paths[@]}"; do for path in "${alt_paths[@]}"; do
if [[ -x "$path" ]]; then if [[ -x $path ]]; then
BROWSER_COMMAND="$path" BROWSER_COMMAND="$path"
echo "✓ Found Thorium browser at: $path" echo "✓ Found Thorium browser at: $path"
found=true found=true
break break
fi fi
done done
if [[ "$found" != true ]]; then if [[ $found != true ]]; then
echo "Error: Thorium browser not found!" echo "Error: Thorium browser not found!"
echo "Please install Thorium browser first or ensure it's in your PATH." echo "Please install Thorium browser first or ensure it's in your PATH."
echo "" echo ""
echo "You can install Thorium browser from:" echo "You can install Thorium browser from:"
echo "https://thorium.rocks/" echo "https://thorium.rocks/"
echo "" echo ""
local continue_anyway=false 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 read -p "Continue anyway? The service will be created but may fail to start (y/N): " -n 1 -r
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
continue_anyway=true continue_anyway=true
fi
else
echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)"
continue_anyway=true
fi
if [[ "$continue_anyway" != true ]]; then
exit 1
fi
fi fi
else else
echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)" echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)"
continue_anyway=true
fi
if [[ $continue_anyway != true ]]; then
exit 1
fi
fi fi
else
echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)"
fi
} }
# Function to create the browser launcher script # Function to create the browser launcher script
create_launcher_script() { create_launcher_script() {
echo "" echo ""
echo "2. Creating Browser Launcher Script..." echo "2. Creating Browser Launcher Script..."
echo "=====================================" echo "====================================="
local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh" local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh"
cat > "$launcher_script" << EOF cat > "$launcher_script" << EOF
#!/bin/bash #!/bin/bash
# Thorium browser launcher for Fitatu website # Thorium browser launcher for Fitatu website
# Created by setup_thorium_startup.sh on $(date) # Created by setup_thorium_startup.sh on $(date)
@ -203,24 +203,24 @@ else
fi fi
EOF EOF
chmod +x "$launcher_script" chmod +x "$launcher_script"
echo "✓ Created launcher script: $launcher_script" echo "✓ Created launcher script: $launcher_script"
} }
# Function to create systemd service for user session # Function to create systemd service for user session
create_user_systemd_service() { create_user_systemd_service() {
echo "" echo ""
echo "3. Creating User Systemd Service..." echo "3. Creating User Systemd Service..."
echo "==================================" echo "=================================="
local user_systemd_dir="$USER_HOME/.config/systemd/user" local user_systemd_dir="$USER_HOME/.config/systemd/user"
local service_file="$user_systemd_dir/thorium-fitatu-startup.service" local service_file="$user_systemd_dir/thorium-fitatu-startup.service"
# Create user systemd directory # Create user systemd directory
sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir" sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir"
# Create the service file # Create the service file
sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF
[Unit] [Unit]
Description=Launch Thorium Browser with Fitatu on Startup Description=Launch Thorium Browser with Fitatu on Startup
After=graphical-session.target After=graphical-session.target
@ -245,18 +245,18 @@ TimeoutStartSec=120
WantedBy=default.target WantedBy=default.target
EOF EOF
echo "✓ Created user systemd service: $service_file" echo "✓ Created user systemd service: $service_file"
} }
# Function to create system-wide systemd service (alternative approach) # Function to create system-wide systemd service (alternative approach)
create_system_systemd_service() { create_system_systemd_service() {
echo "" echo ""
echo "4. Creating System Systemd Service..." echo "4. Creating System Systemd Service..."
echo "====================================" echo "===================================="
local service_file="/etc/systemd/system/thorium-fitatu-startup.service" local service_file="/etc/systemd/system/thorium-fitatu-startup.service"
cat > "$service_file" << EOF cat > "$service_file" << EOF
[Unit] [Unit]
Description=Launch Thorium Browser with Fitatu on Startup Description=Launch Thorium Browser with Fitatu on Startup
After=multi-user.target network-online.target After=multi-user.target network-online.target
@ -283,23 +283,23 @@ TimeoutStartSec=180
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
echo "✓ Created system systemd service: $service_file" echo "✓ Created system systemd service: $service_file"
} }
# Function to create autostart desktop entry (additional method) # Function to create autostart desktop entry (additional method)
create_autostart_entry() { create_autostart_entry() {
echo "" echo ""
echo "5. Creating Autostart Desktop Entry..." echo "5. Creating Autostart Desktop Entry..."
echo "=====================================" echo "====================================="
local autostart_dir="$USER_HOME/.config/autostart" local autostart_dir="$USER_HOME/.config/autostart"
local desktop_file="$autostart_dir/thorium-fitatu.desktop" local desktop_file="$autostart_dir/thorium-fitatu.desktop"
# Create autostart directory # Create autostart directory
sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir" sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir"
# Create desktop entry # Create desktop entry
sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name=Thorium Fitatu Startup Name=Thorium Fitatu Startup
@ -314,45 +314,45 @@ Terminal=false
Categories=Network;WebBrowser; Categories=Network;WebBrowser;
EOF EOF
echo "✓ Created autostart desktop entry: $desktop_file" echo "✓ Created autostart desktop entry: $desktop_file"
} }
# Function to create i3 config autostart entry # Function to create i3 config autostart entry
create_i3_autostart() { create_i3_autostart() {
echo "" echo ""
echo "6. Creating i3 Config Autostart Entry..." echo "6. Creating i3 Config Autostart Entry..."
echo "=======================================" echo "======================================="
local i3_config="$USER_HOME/.config/i3/config" local i3_config="$USER_HOME/.config/i3/config"
local i3_config_dir="$USER_HOME/.config/i3" local i3_config_dir="$USER_HOME/.config/i3"
# Create i3 config directory if it doesn't exist # Create i3 config directory if it doesn't exist
sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir" sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir"
# Check if i3 config exists # Check if i3 config exists
if [[ -f "$i3_config" ]]; then if [[ -f $i3_config ]]; then
# Check if autostart entry already exists # Check if autostart entry already exists
if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then
# Add autostart entry to i3 config # Add autostart entry to i3 config
sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'" sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'"
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'" sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'"
sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'" sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'"
echo "✓ Added autostart entry to i3 config: $i3_config" echo "✓ Added autostart entry to i3 config: $i3_config"
else
echo "✓ Autostart entry already exists in i3 config"
fi
else else
echo "Warning: i3 config file not found at $i3_config" echo "✓ Autostart entry already exists in i3 config"
echo "You may need to manually add the following line to your i3 config:"
echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh"
fi fi
else
echo "Warning: i3 config file not found at $i3_config"
echo "You may need to manually add the following line to your i3 config:"
echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh"
fi
} }
# Function to create a script to enable user service after login # Function to create a script to enable user service after login
create_user_enable_script() { create_user_enable_script() {
local enable_script="$USER_HOME/.config/thorium-enable-service.sh" local enable_script="$USER_HOME/.config/thorium-enable-service.sh"
sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF' sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF'
#!/bin/bash #!/bin/bash
# Script to enable thorium-fitatu-startup user service # Script to enable thorium-fitatu-startup user service
# This runs once to enable the service, then removes itself # This runs once to enable the service, then removes itself
@ -365,110 +365,110 @@ systemctl --user enable thorium-fitatu-startup.service
rm "$0" rm "$0"
EOF EOF
sudo -u "${SUDO_USER}" chmod +x "$enable_script" sudo -u "${SUDO_USER}" chmod +x "$enable_script"
# Add to user's .bashrc to run on next login # Add to user's .bashrc to run on next login
local bashrc="$USER_HOME/.bashrc" 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 '' >> '$bashrc'"
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-enable thorium service (temporary)' >> '$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'" sudo -u "${SUDO_USER}" bash -c "echo '[[ -x ~/.config/thorium-enable-service.sh ]] && ~/.config/thorium-enable-service.sh' >> '$bashrc'"
fi fi
} }
# Function to enable services # Function to enable services
enable_services() { enable_services() {
echo "" echo ""
echo "7. Enabling Services..." echo "7. Enabling Services..."
echo "======================" echo "======================"
# Reload systemd daemon # Reload systemd daemon
systemctl daemon-reload systemctl daemon-reload
echo "✓ System daemon reloaded" echo "✓ System daemon reloaded"
# Enable system service # Enable system service
systemctl enable thorium-fitatu-startup.service systemctl enable thorium-fitatu-startup.service
echo "✓ System service enabled" echo "✓ System service enabled"
# Enable lingering for the user (allows user services to run without login) # Enable lingering for the user (allows user services to run without login)
loginctl enable-linger "${SUDO_USER}" loginctl enable-linger "${SUDO_USER}"
echo "✓ User lingering enabled" echo "✓ User lingering enabled"
# Create a script to enable user service after login # Create a script to enable user service after login
create_user_enable_script create_user_enable_script
echo "✓ User service will be enabled on next login" echo "✓ User service will be enabled on next login"
} }
# Function to test the setup # Function to test the setup
test_setup() { test_setup() {
echo "" echo ""
echo "8. Testing Setup..." echo "8. Testing Setup..."
echo "==================" echo "=================="
local run_test=true local run_test=true
if [[ "$INTERACTIVE_MODE" == "true" ]]; then if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Would you like to test the browser launcher now?" echo "Would you like to test the browser launcher now?"
read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r
echo echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then
run_test=false run_test=false
fi
else
echo "Auto-testing the browser launcher (use --interactive to prompt)"
fi fi
else
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 "Testing browser launch..."
echo "Note: This will open Thorium browser with Fitatu website" echo "Note: This will open Thorium browser with Fitatu website"
# Test the launcher immediately # Test the launcher immediately
if /usr/local/bin/thorium-fitatu-launcher.sh; then if /usr/local/bin/thorium-fitatu-launcher.sh; then
echo "✓ Test launch completed successfully" echo "✓ Test launch completed successfully"
else
echo "✗ Test launch failed"
echo "Check that Thorium browser is properly installed and accessible"
fi
else else
echo "Skipping test launch" echo "✗ Test launch failed"
echo "Check that Thorium browser is properly installed and accessible"
fi fi
else
echo "Skipping test launch"
fi
} }
# Function to show usage instructions # Function to show usage instructions
show_instructions() { show_instructions() {
echo "" echo ""
echo "==========================================" echo "=========================================="
echo "Thorium Browser Auto-Startup Setup Complete" echo "Thorium Browser Auto-Startup Setup Complete"
echo "==========================================" echo "=========================================="
echo "Summary:" echo "Summary:"
echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh" echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh"
echo "✓ System service created: thorium-fitatu-startup.service" echo "✓ System service created: thorium-fitatu-startup.service"
echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service" echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service"
echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop" echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop"
echo "✓ i3 autostart entry added to: ~/.config/i3/config" echo "✓ i3 autostart entry added to: ~/.config/i3/config"
echo "✓ Services enabled for automatic startup" echo "✓ Services enabled for automatic startup"
echo "" echo ""
echo "The system will now:" echo "The system will now:"
echo "• Launch Thorium browser with $TARGET_URL on every startup" echo "• Launch Thorium browser with $TARGET_URL on every startup"
echo "• Use multiple methods to ensure reliable startup" echo "• Use multiple methods to ensure reliable startup"
echo "• Wait for desktop environment to be ready before launching" echo "• Wait for desktop environment to be ready before launching"
echo "• User service will be enabled automatically on next login" echo "• User service will be enabled automatically on next login"
echo "" echo ""
echo "To check status:" echo "To check status:"
echo " systemctl status thorium-fitatu-startup.service" echo " systemctl status thorium-fitatu-startup.service"
echo " systemctl --user status thorium-fitatu-startup.service (after login)" echo " systemctl --user status thorium-fitatu-startup.service (after login)"
echo "" echo ""
echo "To view logs:" echo "To view logs:"
echo " journalctl -u thorium-fitatu-startup.service" echo " journalctl -u thorium-fitatu-startup.service"
echo " journalctl --user -u thorium-fitatu-startup.service" echo " journalctl --user -u thorium-fitatu-startup.service"
echo "" echo ""
echo "To disable (if needed):" echo "To disable (if needed):"
echo " sudo systemctl disable thorium-fitatu-startup.service" echo " sudo systemctl disable thorium-fitatu-startup.service"
echo " systemctl --user disable thorium-fitatu-startup.service" echo " systemctl --user disable thorium-fitatu-startup.service"
echo " rm ~/.config/autostart/thorium-fitatu.desktop" echo " rm ~/.config/autostart/thorium-fitatu.desktop"
echo "" echo ""
echo "IMPORTANT: Browser will launch automatically on next reboot!" echo "IMPORTANT: Browser will launch automatically on next reboot!"
} }
# Main execution # Main execution

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, # 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: # 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 if [[ $# -ge 1 ]]; then
real_bin="$1" real_bin="$1"
shift shift
@ -24,10 +24,10 @@ if [[ ! -x "$real_bin" || "$prog_name" == "browser-preexec-wrapper.sh" ]]; then
fi fi
# Best-effort: install hosts file quietly; don't block browser startup # Best-effort: install hosts file quietly; don't block browser startup
if command -v sudo >/dev/null 2>&1; then if command -v sudo > /dev/null 2>&1; then
sudo -n "$HOSTS_INSTALL_SCRIPT" >/dev/null 2>&1 || true sudo -n "$HOSTS_INSTALL_SCRIPT" > /dev/null 2>&1 || true
else else
"$HOSTS_INSTALL_SCRIPT" >/dev/null 2>&1 || true "$HOSTS_INSTALL_SCRIPT" > /dev/null 2>&1 || true
fi fi
exec "$real_bin" "$@" exec "$real_bin" "$@"

View File

@ -11,100 +11,100 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__"
# Function to log with timestamp # Function to log with timestamp
log_message() { log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2 echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2
} }
# Function to check if hosts file needs restoration # Function to check if hosts file needs restoration
needs_restoration() { needs_restoration() {
# Check if file exists # Check if file exists
if [[ ! -f "$HOSTS_FILE" ]]; then if [[ ! -f $HOSTS_FILE ]]; then
return 0 # File missing, needs restoration return 0 # File missing, needs restoration
fi fi
# Check if file is empty or too small (less than 1000 lines indicates tampering) # Check if file is empty or too small (less than 1000 lines indicates tampering)
local line_count local line_count
line_count=$(wc -l < "$HOSTS_FILE" 2>/dev/null || echo "0") line_count=$(wc -l < "$HOSTS_FILE" 2> /dev/null || echo "0")
if [[ "$line_count" -lt 1000 ]]; then if [[ $line_count -lt 1000 ]]; then
return 0 # File too small, likely tampered with return 0 # File too small, likely tampered with
fi fi
# Check if our custom entries are missing # 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 return 0 # Our custom entries missing, needs restoration
fi fi
# Check if StevenBlack entries are missing # 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 return 0 # StevenBlack entries missing, needs restoration
fi fi
return 1 # File seems intact return 1 # File seems intact
} }
# Function to restore hosts file # Function to restore hosts file
restore_hosts_file() { restore_hosts_file() {
log_message "Hosts file modification detected - initiating restoration" 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" log_message "Running hosts installation script: $HOSTS_INSTALL_SCRIPT"
if bash "$HOSTS_INSTALL_SCRIPT" >> "$LOG_FILE" 2>&1; then if bash "$HOSTS_INSTALL_SCRIPT" >> "$LOG_FILE" 2>&1; then
log_message "Hosts file restoration completed successfully" log_message "Hosts file restoration completed successfully"
else
log_message "Hosts file restoration failed with exit code $?"
fi
else else
log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT" log_message "Hosts file restoration failed with exit code $?"
fi fi
else
log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT"
fi
} }
# Function to monitor with inotifywait # Function to monitor with inotifywait
monitor_with_inotify() { monitor_with_inotify() {
log_message "Starting hosts file monitoring with inotify" log_message "Starting hosts file monitoring with inotify"
# Monitor the hosts file and its directory for various events # 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 while read -r file event time; do
# Check if the event is related to our hosts file # 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" log_message "Event detected: $event on $file at $time"
# Small delay to avoid rapid-fire events # Small delay to avoid rapid-fire events
sleep 2 sleep 2
# Check if restoration is needed # Check if restoration is needed
if needs_restoration; then if needs_restoration; then
restore_hosts_file restore_hosts_file
else else
log_message "Hosts file check passed - no restoration needed" log_message "Hosts file check passed - no restoration needed"
fi
fi fi
fi
done done
} }
# Function to monitor with polling (fallback) # Function to monitor with polling (fallback)
monitor_with_polling() { monitor_with_polling() {
log_message "Starting hosts file monitoring with polling (fallback method)" log_message "Starting hosts file monitoring with polling (fallback method)"
while true; do while true; do
if needs_restoration; then if needs_restoration; then
restore_hosts_file restore_hosts_file
fi fi
# Check every 30 seconds # Check every 30 seconds
sleep 30 sleep 30
done done
} }
# Main execution # Main execution
log_message "=== Hosts File Monitor Started ===" log_message "=== Hosts File Monitor Started ==="
# Check if inotify-tools is available # 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" log_message "Using inotify for file monitoring"
monitor_with_inotify monitor_with_inotify
else else
log_message "inotify-tools not available, using polling method" log_message "inotify-tools not available, using polling method"
log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools" log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools"
monitor_with_polling monitor_with_polling
fi fi

View File

@ -13,30 +13,30 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__"
# Function to log with timestamp # Function to log with timestamp
log_message() { log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE" echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
} }
# Function to execute with logging # Function to execute with logging
execute_with_log() { execute_with_log() {
local script_path="$1" local script_path="$1"
local script_name="$2" local script_name="$2"
log_message "Starting $script_name" log_message "Starting $script_name"
echo "Executing $script_name..." >&2 echo "Executing $script_name..." >&2
if [[ -f "$script_path" ]]; then if [[ -f $script_path ]]; then
if bash "$script_path" >> "$LOG_FILE" 2>&1; then if bash "$script_path" >> "$LOG_FILE" 2>&1; then
log_message "$script_name completed successfully" log_message "$script_name completed successfully"
echo "$script_name completed successfully" >&2 echo "$script_name completed successfully" >&2
else
local ec=$?
log_message "$script_name failed with exit code $ec"
echo "$script_name failed (exit $ec)" >&2
fi
else else
log_message "$script_name not found at $script_path" local ec=$?
echo "$script_name not found at $script_path" >&2 log_message "$script_name failed with exit code $ec"
echo "$script_name failed (exit $ec)" >&2
fi fi
else
log_message "$script_name not found at $script_path"
echo "$script_name not found at $script_path" >&2
fi
} }
# Start maintenance # Start maintenance

View File

@ -5,38 +5,37 @@
set -euo pipefail set -euo pipefail
DOWNLOADS_DIR="$HOME/Downloads" DOWNLOADS_DIR="$HOME/Downloads"
HOME_DIR="$HOME"
# Test function # Test function
test_file_removal() { test_file_removal() {
local files=() local files=()
# Find a few test files # Find a few test files
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
files+=("$file") 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:" echo "Found ${#files[@]} test files:"
for file in "${files[@]}"; do for file in "${files[@]}"; do
echo " - $file" echo " - $file"
done done
echo "Attempting to remove files..." echo "Attempting to remove files..."
local removed=0 local removed=0
local failed=0 local failed=0
for file in "${files[@]}"; do for file in "${files[@]}"; do
echo "Removing: $file" echo "Removing: $file"
if rm "$file" 2>/dev/null; then if rm "$file" 2> /dev/null; then
echo " SUCCESS" echo " SUCCESS"
((removed++)) ((removed++))
else else
echo " FAILED (exit code: $?)" echo " FAILED (exit code: $?)"
((failed++)) ((failed++))
fi fi
done done
echo "Results: $removed removed, $failed failed" echo "Results: $removed removed, $failed failed"
} }
test_file_removal test_file_removal

View File

@ -2,21 +2,21 @@
# Check if input and output files are provided # Check if input and output files are provided
if [ $# -ne 2 ]; then if [ $# -ne 2 ]; then
echo "Usage: $0 input_file.txt output_file.txt" echo "Usage: $0 input_file.txt output_file.txt"
exit 1 exit 1
fi fi
# Check if the input file exists # Check if the input file exists
if [ ! -f "$1" ]; then if [ ! -f "$1" ]; then
echo "Error: File '$1' not found" echo "Error: File '$1' not found"
exit 1 exit 1
fi fi
# Store output file name # Store output file name
output_file="$2" output_file="$2"
# Clear output file at the beginning # Clear output file at the beginning
> "$output_file" : > "$output_file"
# Process file using a pipeline of specialized tools # Process file using a pipeline of specialized tools
# 1. tr - remove non-alphabetic chars except newlines # 1. tr - remove non-alphabetic chars except newlines

View File

@ -13,7 +13,7 @@ SAMPLE_LIMIT=20
# Simple usage helper # Simple usage helper
usage() { usage() {
cat <<EOF cat << EOF
Usage: $(basename "$0") [--dry-run|-n] [--sample=N] Usage: $(basename "$0") [--dry-run|-n] [--sample=N]
Options: Options:
@ -36,368 +36,372 @@ VIDEO_EXTENSIONS=("mp4" "avi" "mkv" "mov" "wmv" "flv" "webm" "m4v" "3gp" "ogv" "
# Function to log messages with timestamp # Function to log messages with timestamp
log() { log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
} }
# Parse CLI flags early # Parse CLI flags early
while [[ ${1:-} =~ ^- ]]; do while [[ ${1:-} =~ ^- ]]; do
case "${1}" in case "${1}" in
-n|--dry-run) -n | --dry-run)
DRY_RUN=true DRY_RUN=true
shift shift
;; ;;
--sample=*) --sample=*)
SAMPLE_LIMIT="${1#*=}" SAMPLE_LIMIT="${1#*=}"
shift shift
;; ;;
-h|--help) -h | --help)
usage usage
exit 0 exit 0
;; ;;
*) *)
echo "Unknown option: $1" >&2 echo "Unknown option: $1" >&2
usage usage
exit 1 exit 1
;; ;;
esac esac
done done
# Function to check if file has media extension # Function to check if file has media extension
is_media_file() { is_media_file() {
local file="$1" local file="$1"
local extension="${file##*.}" local extension="${file##*.}"
extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]') extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]')
# Check if it's an image # Check if it's an image
for ext in "${IMAGE_EXTENSIONS[@]}"; do for ext in "${IMAGE_EXTENSIONS[@]}"; do
if [[ "$extension" == "$ext" ]]; then if [[ $extension == "$ext" ]]; then
return 0 return 0
fi fi
done done
# Check if it's a video # Check if it's a video
for ext in "${VIDEO_EXTENSIONS[@]}"; do for ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ "$extension" == "$ext" ]]; then if [[ $extension == "$ext" ]]; then
return 0 return 0
fi fi
done done
return 1 return 1
} }
# Function to find media files in a directory (non-recursive for home, avoid common system dirs) # Function to find media files in a directory (non-recursive for home, avoid common system dirs)
find_media_files() { find_media_files() {
local search_dir="$1" local search_dir="$1"
local files=() local files=()
# Directories to exclude under Downloads # Directories to exclude under Downloads
local -a EXCLUDES=( local -a EXCLUDES=(
".git" ".hg" ".svn" ".cache" "node_modules" "dist" "build" "out" "target" "coverage" "__pycache__" "venv" ".venv" ".git" ".hg" ".svn" ".cache" "node_modules" "dist" "build" "out" "target" "coverage" "__pycache__" "venv" ".venv"
# previous staging dirs created by this script # previous staging dirs created by this script
".media_organize_" "media_organize_" ".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) # For home directory, only check files directly in ~ (not subdirectories)
# Exclude common system/config directories # Exclude common system/config directories
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
local basename=$(basename "$file") local basename
# Skip hidden files and common system directories basename=$(basename "$file")
if [[ ! "$basename" =~ ^\. ]] && [[ -f "$file" ]]; then # Skip hidden files and common system directories
if is_media_file "$file"; then if [[ ! $basename =~ ^\. ]] && [[ -f $file ]]; then
files+=("$file") if is_media_file "$file"; then
fi files+=("$file")
fi fi
done < <(find "$search_dir" -maxdepth 1 -type f -print0 2>/dev/null) fi
else done < <(find "$search_dir" -maxdepth 1 -type f -print0 2> /dev/null)
# For Downloads, search recursively, pruning excluded directories else
# Build prune expression # For Downloads, search recursively, pruning excluded directories
local prune_expr=() # Build prune expression
for ex in "${EXCLUDES[@]}"; do local prune_expr=()
prune_expr+=( -name "$ex*" -o ) for ex in "${EXCLUDES[@]}"; do
done prune_expr+=(-name "$ex*" -o)
# Remove trailing -o done
unset 'prune_expr[${#prune_expr[@]}-1]' # Remove trailing -o
unset 'prune_expr[${#prune_expr[@]}-1]'
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
if is_media_file "$file"; then if is_media_file "$file"; then
files+=("$file") files+=("$file")
fi 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 fi
printf '%s\n' "${files[@]}" printf '%s\n' "${files[@]}"
} }
# Function to create timestamped zip archive # Function to create timestamped zip archive
create_media_archive() { create_media_archive() {
local files=("$@") local files=("$@")
if [[ ${#files[@]} -eq 0 ]]; then if [[ ${#files[@]} -eq 0 ]]; then
log "No media files found to archive." log "No media files found to archive."
return 0 return 0
fi fi
# Create timestamp for archive name # Create timestamp for archive name
local timestamp=$(date '+%Y%m%d_%H%M%S') local timestamp
local archive_name="media_archive_${timestamp}.zip" timestamp=$(date '+%Y%m%d_%H%M%S')
local archive_path="$DOWNLOADS_DIR/$archive_name" local archive_name="media_archive_${timestamp}.zip"
local archive_path="$DOWNLOADS_DIR/$archive_name"
# Create temporary directory (fallback to /tmp if needed) # 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_$$" TEMP_DIR="/tmp/media_organize_$$"
mkdir -p "$TEMP_DIR" mkdir -p "$TEMP_DIR"
fi fi
# Ensure temp dir is cleaned up on function return; trap unsets itself after running # Ensure temp dir is cleaned up on function return; trap unsets itself after running
trap 'rm -rf "$TEMP_DIR" 2>/dev/null || true; trap - RETURN' RETURN trap 'rm -rf "$TEMP_DIR" 2>/dev/null || true; trap - RETURN' RETURN
log "Found ${#files[@]} media files to archive." log "Found ${#files[@]} media files to archive."
log "Creating archive: $archive_path" log "Creating archive: $archive_path"
# Copy files to temp directory maintaining relative structure # Copy files to temp directory maintaining relative structure
local successfully_copied=() local successfully_copied=()
local copy_errors=0 local copy_errors=0
for file in "${files[@]}"; do for file in "${files[@]}"; do
if [[ -f "$file" ]]; then if [[ -f $file ]]; then
local relative_path="" local relative_path=""
if [[ "$file" == "$DOWNLOADS_DIR"* ]]; then if [[ $file == "$DOWNLOADS_DIR"* ]]; then
relative_path="downloads/${file#$DOWNLOADS_DIR/}" relative_path="downloads/${file#"$DOWNLOADS_DIR"/}"
else else
relative_path="home/${file#$HOME_DIR/}" relative_path="home/${file#"$HOME_DIR"/}"
fi fi
local temp_file="$TEMP_DIR/$relative_path" 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" mkdir -p "$temp_dir"
# Check readability first to provide a clearer error # Check readability first to provide a clearer error
if [[ ! -r "$file" ]]; then if [[ ! -r $file ]]; then
log "WARNING: Cannot read $file (permission denied?)" log "WARNING: Cannot read $file (permission denied?)"
((copy_errors++)) ((copy_errors++))
continue continue
fi fi
# Attempt copy and capture any error for logging # Attempt copy and capture any error for logging
local cp_err local cp_err
if cp_err=$(cp -p "$file" "$temp_file" 2>&1); then if cp_err=$(cp -p "$file" "$temp_file" 2>&1); then
successfully_copied+=("$file") successfully_copied+=("$file")
else else
# Surface the cp error so the user can see the reason # Surface the cp error so the user can see the reason
log "WARNING: Failed to copy $file -> $cp_err" log "WARNING: Failed to copy $file -> $cp_err"
# Special hint for space issues # Special hint for space issues
if echo "$cp_err" | grep -qi "No space left on device"; then if echo "$cp_err" | grep -qi "No space left on device"; then
log "HINT: Not enough free space to stage files. Using $TEMP_DIR. Free up space or change TEMP_DIR." log "HINT: Not enough free space to stage files. Using $TEMP_DIR. Free up space or change TEMP_DIR."
fi
((copy_errors++))
fi
fi fi
((copy_errors++))
fi
fi
done
if [[ ${#successfully_copied[@]} -eq 0 ]]; then
log "ERROR: No files were successfully copied to temp directory."
rm -rf "$TEMP_DIR"
return 1
fi
if [[ $copy_errors -gt 0 ]]; then
log "WARNING: $copy_errors files failed to copy."
fi
# Create zip archive with maximum compression
log "Creating zip archive with ${#successfully_copied[@]} files..."
cd "$TEMP_DIR"
if zip -9 -r "$archive_path" . 2>&1; then
log "Successfully created archive with ${#successfully_copied[@]} files."
# Verify the zip file was actually created and is not empty
if [[ ! -f $archive_path ]]; then
log "ERROR: Archive file was not created at $archive_path"
rm -rf "$TEMP_DIR"
return 1
fi
local archive_size
archive_size=$(stat -c%s "$archive_path" 2> /dev/null || echo "0")
if [[ $archive_size -eq 0 ]]; then
log "ERROR: Archive file is empty"
rm -rf "$TEMP_DIR"
return 1
fi
# Remove original files only if zip was successful
local removed_count=0
local remove_errors=0
log "Starting to remove ${#successfully_copied[@]} original files..."
# Temporarily disable strict error handling for file removal
set +e
for file in "${successfully_copied[@]}"; do
if [[ -f $file ]]; then
if rm "$file" 2> /dev/null; then
removed_count=$((removed_count + 1))
log "Removed: $(basename "$file")"
else
remove_errors=$((remove_errors + 1))
log "ERROR: Failed to remove $(basename "$file")"
fi
else
log "WARNING: File no longer exists: $(basename "$file")"
fi
done done
if [[ ${#successfully_copied[@]} -eq 0 ]]; then # Re-enable strict error handling
log "ERROR: No files were successfully copied to temp directory." set -e
rm -rf "$TEMP_DIR"
return 1 log "Successfully removed $removed_count original files."
if [[ $remove_errors -gt 0 ]]; then
log "WARNING: Failed to remove $remove_errors files."
fi fi
log "Archive size: $(du -h "$archive_path" | cut -f1)"
if [[ $copy_errors -gt 0 ]]; then
log "WARNING: $copy_errors files failed to copy."
fi
# Create zip archive with maximum compression
log "Creating zip archive with ${#successfully_copied[@]} files..."
cd "$TEMP_DIR"
if zip -9 -r "$archive_path" . 2>&1; then
log "Successfully created archive with ${#successfully_copied[@]} files."
# Verify the zip file was actually created and is not empty
if [[ ! -f "$archive_path" ]]; then
log "ERROR: Archive file was not created at $archive_path"
rm -rf "$TEMP_DIR"
return 1
fi
local archive_size=$(stat -c%s "$archive_path" 2>/dev/null || echo "0")
if [[ "$archive_size" -eq 0 ]]; then
log "ERROR: Archive file is empty"
rm -rf "$TEMP_DIR"
return 1
fi
# Remove original files only if zip was successful
local removed_count=0
local remove_errors=0
log "Starting to remove ${#successfully_copied[@]} original files..."
# Temporarily disable strict error handling for file removal
set +e
for file in "${successfully_copied[@]}"; do
if [[ -f "$file" ]]; then
if rm "$file" 2>/dev/null; then
removed_count=$((removed_count + 1))
log "Removed: $(basename "$file")"
else
remove_errors=$((remove_errors + 1))
log "ERROR: Failed to remove $(basename "$file")"
fi
else
log "WARNING: File no longer exists: $(basename "$file")"
fi
done
# Re-enable strict error handling
set -e
log "Successfully removed $removed_count original files."
if [[ $remove_errors -gt 0 ]]; then
log "WARNING: Failed to remove $remove_errors files."
fi
log "Archive size: $(du -h "$archive_path" | cut -f1)"
# Cleanup temp directory (trap will also attempt, which is safe) # Cleanup temp directory (trap will also attempt, which is safe)
rm -rf "$TEMP_DIR" rm -rf "$TEMP_DIR"
# Return success only if we removed files or there were no files to remove # Return success only if we removed files or there were no files to remove
if [[ $removed_count -gt 0 ]] || [[ ${#successfully_copied[@]} -eq 0 ]]; then if [[ $removed_count -gt 0 ]] || [[ ${#successfully_copied[@]} -eq 0 ]]; then
return 0 return 0
else
log "ERROR: Failed to remove any files after successful archive creation."
return 1
fi
else else
log "ERROR: Failed to create archive. Original files preserved." log "ERROR: Failed to remove any files after successful archive creation."
log "Zip command failed." return 1
rm -rf "$TEMP_DIR"
return 1
fi fi
else
log "ERROR: Failed to create archive. Original files preserved."
log "Zip command failed."
rm -rf "$TEMP_DIR"
return 1
fi
} }
# Main execution # Main execution
main() { main() {
log "Starting media file organization..." log "Starting media file organization..."
# Check if required directories exist # Check if required directories exist
if [[ ! -d "$DOWNLOADS_DIR" ]]; then if [[ ! -d $DOWNLOADS_DIR ]]; then
log "ERROR: Downloads directory not found: $DOWNLOADS_DIR" log "ERROR: Downloads directory not found: $DOWNLOADS_DIR"
exit 1 exit 1
fi
if [[ ! -d $HOME_DIR ]]; then
log "ERROR: Home directory not found: $HOME_DIR"
exit 1
fi
# Check if zip command is available
if ! command -v zip > /dev/null 2>&1; then
log "ERROR: zip command not found. Please install zip package."
exit 1
fi
# Find all media files
log "Scanning for media files..."
local all_files=()
# Find files in Downloads directory
log "Scanning Downloads directory..."
while IFS= read -r file; do
[[ -n $file ]] && all_files+=("$file")
done < <(find_media_files "$DOWNLOADS_DIR")
# Find files in home directory (only direct files, not subdirectories)
log "Scanning home directory (root level only)..."
while IFS= read -r file; do
[[ -n $file ]] && all_files+=("$file")
done < <(find_media_files "$HOME_DIR")
if $DRY_RUN; then
log "Dry-run mode: summarizing what would be archived."
if [[ ${#all_files[@]} -eq 0 ]]; then
log "No media files found to organize."
exit 0
fi fi
if [[ ! -d "$HOME_DIR" ]]; then # Count by extension
log "ERROR: Home directory not found: $HOME_DIR" declare -A ext_counts=()
exit 1 # Count by top-level directory under Downloads
declare -A dir_counts=()
# Sample paths for suspect extensions
declare -A samples_ts=()
declare -A samples_svg=()
declare -A samples_ico=()
for f in "${all_files[@]}"; do
# Extension
ext="${f##*.}"
ext="${ext,,}"
((ext_counts["$ext"]++)) || true
# Top directory under Downloads
if [[ $f == "$DOWNLOADS_DIR"/* ]]; then
rel="${f#"$DOWNLOADS_DIR"/}"
topdir="${rel%%/*}"
[[ $topdir == "$rel" ]] && topdir="."
((dir_counts["$topdir"]++)) || true
else
((dir_counts["~"]++)) || true
fi
# Samples for suspect extensions
case "$ext" in
ts)
if [[ ${#samples_ts[@]} -lt $SAMPLE_LIMIT ]]; then samples_ts["$f"]=1; fi
;;
svg)
if [[ ${#samples_svg[@]} -lt $SAMPLE_LIMIT ]]; then samples_svg["$f"]=1; fi
;;
ico)
if [[ ${#samples_ico[@]} -lt $SAMPLE_LIMIT ]]; then samples_ico["$f"]=1; fi
;;
esac
done
echo ""
echo "Summary by extension (top 20):"
for k in "${!ext_counts[@]}"; do
printf "%8d %s\n" "${ext_counts[$k]}" "$k"
done | sort -nr | head -n 20
echo ""
echo "Top contributing directories under Downloads (top 20):"
for k in "${!dir_counts[@]}"; do
printf "%8d %s\n" "${dir_counts[$k]}" "$k"
done | sort -nr | head -n 20
echo ""
if [[ ${#samples_ts[@]} -gt 0 ]]; then
echo "Sample .ts files (TypeScript vs Transport Stream) up to $SAMPLE_LIMIT:"
for p in "${!samples_ts[@]}"; do echo " $p"; done | sort
echo ""
fi
if [[ ${#samples_svg[@]} -gt 0 ]]; then
echo "Sample .svg files up to $SAMPLE_LIMIT:"
for p in "${!samples_svg[@]}"; do echo " $p"; done | sort
echo ""
fi
if [[ ${#samples_ico[@]} -gt 0 ]]; then
echo "Sample .ico files up to $SAMPLE_LIMIT:"
for p in "${!samples_ico[@]}"; do echo " $p"; done | sort
echo ""
fi fi
# Check if zip command is available echo "Total files that would be archived: ${#all_files[@]}"
if ! command -v zip >/dev/null 2>&1; then echo "(Use: $(basename "$0") --dry-run --sample=50 to see more examples.)"
log "ERROR: zip command not found. Please install zip package." exit 0
exit 1 fi
fi
# Find all media files # Create archive if files found
log "Scanning for media files..." if [[ ${#all_files[@]} -gt 0 ]]; then
local all_files=() create_media_archive "${all_files[@]}"
log "Media organization completed successfully."
# Find files in Downloads directory else
log "Scanning Downloads directory..." log "No media files found to organize."
while IFS= read -r file; do fi
[[ -n "$file" ]] && all_files+=("$file")
done < <(find_media_files "$DOWNLOADS_DIR")
# Find files in home directory (only direct files, not subdirectories)
log "Scanning home directory (root level only)..."
while IFS= read -r file; do
[[ -n "$file" ]] && all_files+=("$file")
done < <(find_media_files "$HOME_DIR")
if $DRY_RUN; then
log "Dry-run mode: summarizing what would be archived."
if [[ ${#all_files[@]} -eq 0 ]]; then
log "No media files found to organize."
exit 0
fi
# Count by extension
declare -A ext_counts=()
# Count by top-level directory under Downloads
declare -A dir_counts=()
# Sample paths for suspect extensions
declare -A samples_ts=()
declare -A samples_svg=()
declare -A samples_ico=()
for f in "${all_files[@]}"; do
# Extension
ext="${f##*.}"
ext="${ext,,}"
((ext_counts["$ext"]++)) || true
# Top directory under Downloads
if [[ "$f" == "$DOWNLOADS_DIR"/* ]]; then
rel="${f#"$DOWNLOADS_DIR"/}"
topdir="${rel%%/*}"
[[ "$topdir" == "$rel" ]] && topdir="."
((dir_counts["$topdir"]++)) || true
else
((dir_counts["~"]++)) || true
fi
# Samples for suspect extensions
case "$ext" in
ts)
if [[ ${#samples_ts[@]} -lt $SAMPLE_LIMIT ]]; then samples_ts["$f"]=1; fi
;;
svg)
if [[ ${#samples_svg[@]} -lt $SAMPLE_LIMIT ]]; then samples_svg["$f"]=1; fi
;;
ico)
if [[ ${#samples_ico[@]} -lt $SAMPLE_LIMIT ]]; then samples_ico["$f"]=1; fi
;;
esac
done
echo ""
echo "Summary by extension (top 20):"
for k in "${!ext_counts[@]}"; do
printf "%8d %s\n" "${ext_counts[$k]}" "$k"
done | sort -nr | head -n 20
echo ""
echo "Top contributing directories under Downloads (top 20):"
for k in "${!dir_counts[@]}"; do
printf "%8d %s\n" "${dir_counts[$k]}" "$k"
done | sort -nr | head -n 20
echo ""
if [[ ${#samples_ts[@]} -gt 0 ]]; then
echo "Sample .ts files (TypeScript vs Transport Stream) up to $SAMPLE_LIMIT:"
for p in "${!samples_ts[@]}"; do echo " $p"; done | sort
echo ""
fi
if [[ ${#samples_svg[@]} -gt 0 ]]; then
echo "Sample .svg files up to $SAMPLE_LIMIT:"
for p in "${!samples_svg[@]}"; do echo " $p"; done | sort
echo ""
fi
if [[ ${#samples_ico[@]} -gt 0 ]]; then
echo "Sample .ico files up to $SAMPLE_LIMIT:"
for p in "${!samples_ico[@]}"; do echo " $p"; done | sort
echo ""
fi
echo "Total files that would be archived: ${#all_files[@]}"
echo "(Use: $(basename "$0") --dry-run --sample=50 to see more examples.)"
exit 0
fi
# Create archive if files found
if [[ ${#all_files[@]} -gt 0 ]]; then
create_media_archive "${all_files[@]}"
log "Media organization completed successfully."
else
log "No media files found to organize."
fi
} }
# Run main function # Run main function

View File

@ -13,20 +13,20 @@ USER_NAME="$(whoami)"
# Function to log messages # Function to log messages
log() { log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
} }
# Check if organize script exists # 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" log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
exit 1 exit 1
fi fi
# Check if running as root for systemd service creation # Check if running as root for systemd service creation
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
log "This script needs to be run as root to create systemd service." log "This script needs to be run as root to create systemd service."
log "Please run: sudo $0" log "Please run: sudo $0"
exit 1 exit 1
fi fi
log "Setting up media organizer startup service..." log "Setting up media organizer startup service..."

View File

@ -6,34 +6,34 @@
# --reboot: Offer to reboot after setup completion # --reboot: Offer to reboot after setup completion
# --logout: Allow restart of LightDM (which will logout the user) # --logout: Allow restart of LightDM (which will logout the user)
set -e # Exit on any error set -e # Exit on any error
# Check for flags # Check for flags
OFFER_REBOOT=false OFFER_REBOOT=false
ALLOW_LOGOUT=false ALLOW_LOGOUT=false
for arg in "$@"; do for arg in "$@"; do
case $arg in case $arg in
--reboot) --reboot)
OFFER_REBOOT=true OFFER_REBOOT=true
shift shift
;; ;;
--logout) --logout)
ALLOW_LOGOUT=true ALLOW_LOGOUT=true
shift shift
;; ;;
*) *)
# Unknown option, keep it for sudo check # Unknown option, keep it for sudo check
;; ;;
esac esac
done done
# Function to check and request sudo privileges # Function to check and request sudo privileges
check_sudo() { check_sudo() {
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to modify system configurations." echo "This script requires sudo privileges to modify system configurations."
echo "Requesting sudo access..." echo "Requesting sudo access..."
exec sudo "$0" "$@" exec sudo "$0" "$@"
fi fi
} }
# Check for sudo privileges first # Check for sudo privileges first
@ -46,9 +46,9 @@ echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}" echo "Original user: ${SUDO_USER:-$USER}"
# Verify we have a valid 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." echo "Error: Could not determine the original user. Please run this script with sudo."
exit 1 exit 1
fi fi
TARGET_USER="${SUDO_USER}" TARGET_USER="${SUDO_USER}"
@ -56,24 +56,26 @@ echo "Target user for configuration: $TARGET_USER"
# Function to backup files # Function to backup files
backup_file() { backup_file() {
local file="$1" local file="$1"
if [[ -f "$file" ]]; then if [[ -f $file ]]; then
local backup="${file}.backup.$(date +%Y%m%d_%H%M%S)" local backup timestamp
cp "$file" "$backup" timestamp=$(date +%Y%m%d_%H%M%S)
echo "✓ Backed up $file to $backup" backup="${file}.backup.$timestamp"
fi cp "$file" "$backup"
echo "✓ Backed up $file to $backup"
fi
} }
# Function to configure passwordless sudo # Function to configure passwordless sudo
configure_passwordless_sudo() { configure_passwordless_sudo() {
echo "" echo ""
echo "1. Configuring Passwordless Sudo..." echo "1. Configuring Passwordless Sudo..."
echo "==================================" echo "=================================="
local sudoers_file="/etc/sudoers.d/99-passwordless-${TARGET_USER}" local sudoers_file="/etc/sudoers.d/99-passwordless-${TARGET_USER}"
# Create sudoers configuration for passwordless access # Create sudoers configuration for passwordless access
cat > "$sudoers_file" << EOF cat > "$sudoers_file" << EOF
# Passwordless sudo configuration for user: ${TARGET_USER} # Passwordless sudo configuration for user: ${TARGET_USER}
# Created by setup_passwordless_system.sh on $(date) # Created by setup_passwordless_system.sh on $(date)
# WARNING: This allows the user to run any command without password # WARNING: This allows the user to run any command without password
@ -88,55 +90,55 @@ Defaults:${TARGET_USER} !requiretty
Defaults:${TARGET_USER} env_keep += "HOME PATH DISPLAY XAUTHORITY" Defaults:${TARGET_USER} env_keep += "HOME PATH DISPLAY XAUTHORITY"
EOF EOF
# Set proper permissions for sudoers file # Set proper permissions for sudoers file
chmod 440 "$sudoers_file" chmod 440 "$sudoers_file"
# Verify the sudoers file syntax # Verify the sudoers file syntax
if visudo -c -f "$sudoers_file"; then if visudo -c -f "$sudoers_file"; then
echo "✓ Passwordless sudo configured for user: $TARGET_USER" echo "✓ Passwordless sudo configured for user: $TARGET_USER"
echo "✓ Sudoers file created: $sudoers_file" echo "✓ Sudoers file created: $sudoers_file"
else else
echo "✗ Error: Invalid sudoers syntax. Removing file for safety." echo "✗ Error: Invalid sudoers syntax. Removing file for safety."
rm -f "$sudoers_file" rm -f "$sudoers_file"
exit 1 exit 1
fi fi
} }
# Function to configure lightdm auto-login # Function to configure lightdm auto-login
configure_lightdm_autologin() { configure_lightdm_autologin() {
echo "" echo ""
echo "2. Configuring LightDM Auto-Login..." echo "2. Configuring LightDM Auto-Login..."
echo "===================================" echo "==================================="
local lightdm_conf="/etc/lightdm/lightdm.conf" local lightdm_conf="/etc/lightdm/lightdm.conf"
local lightdm_conf_dir="/etc/lightdm/lightdm.conf.d" local lightdm_conf_dir="/etc/lightdm/lightdm.conf.d"
local custom_conf="$lightdm_conf_dir/50-autologin.conf" local custom_conf="$lightdm_conf_dir/50-autologin.conf"
# Create lightdm config directory if it doesn't exist # Create lightdm config directory if it doesn't exist
mkdir -p "$lightdm_conf_dir" mkdir -p "$lightdm_conf_dir"
# Backup existing lightdm configuration # Backup existing lightdm configuration
backup_file "$lightdm_conf" backup_file "$lightdm_conf"
# Check if lightdm is installed # Check if lightdm is installed
if ! command -v lightdm &> /dev/null; then if ! command -v lightdm &> /dev/null; then
echo "Warning: LightDM not found. Installing lightdm..." echo "Warning: LightDM not found. Installing lightdm..."
pacman -S --noconfirm lightdm lightdm-gtk-greeter pacman -S --noconfirm lightdm lightdm-gtk-greeter
fi fi
# Method 1: Update the main lightdm.conf file directly # Method 1: Update the main lightdm.conf file directly
sed -i "/^#autologin-user=/c\autologin-user=${TARGET_USER}" "$lightdm_conf" sed -i "/^#autologin-user=/c\autologin-user=${TARGET_USER}" "$lightdm_conf"
sed -i "/^#autologin-user-timeout=/c\autologin-user-timeout=0" "$lightdm_conf" sed -i "/^#autologin-user-timeout=/c\autologin-user-timeout=0" "$lightdm_conf"
sed -i "/^#autologin-session=/c\autologin-session=i3" "$lightdm_conf" sed -i "/^#autologin-session=/c\autologin-session=i3" "$lightdm_conf"
sed -i "/^#autologin-in-background=/c\autologin-in-background=false" "$lightdm_conf" sed -i "/^#autologin-in-background=/c\autologin-in-background=false" "$lightdm_conf"
# Also set user-session to i3 as fallback # Also set user-session to i3 as fallback
sed -i "/^#user-session=/c\user-session=i3" "$lightdm_conf" sed -i "/^#user-session=/c\user-session=i3" "$lightdm_conf"
echo "✓ LightDM auto-login configured in main config file" echo "✓ LightDM auto-login configured in main config file"
# Method 2: Also create the separate config file for redundancy # Method 2: Also create the separate config file for redundancy
cat > "$custom_conf" << EOF cat > "$custom_conf" << EOF
# LightDM Auto-Login Configuration # LightDM Auto-Login Configuration
# Created by setup_passwordless_system.sh on $(date) # Created by setup_passwordless_system.sh on $(date)
@ -158,37 +160,37 @@ greeter-session=lightdm-gtk-greeter
autologin-in-background=false autologin-in-background=false
EOF EOF
echo "✓ LightDM auto-login also configured in separate config file: $custom_conf" echo "✓ LightDM auto-login also configured in separate config file: $custom_conf"
# Enable lightdm service # Enable lightdm service
systemctl enable lightdm.service systemctl enable lightdm.service
echo "✓ LightDM service enabled" echo "✓ LightDM service enabled"
# Restart lightdm to apply changes only if --logout flag is provided # 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..." echo "Restarting LightDM to apply auto-login settings..."
systemctl restart lightdm.service systemctl restart lightdm.service
echo "✓ LightDM restarted" echo "✓ LightDM restarted"
else else
echo "✓ LightDM configuration complete (restart lightdm or reboot to activate auto-login)" echo "✓ LightDM configuration complete (restart lightdm or reboot to activate auto-login)"
fi fi
} }
# Function to configure i3 session # Function to configure i3 session
configure_i3_session() { configure_i3_session() {
echo "" echo ""
echo "3. Configuring i3 Session..." echo "3. Configuring i3 Session..."
echo "===========================" echo "==========================="
local xsessions_dir="/usr/share/xsessions" local xsessions_dir="/usr/share/xsessions"
local i3_desktop="$xsessions_dir/i3.desktop" local i3_desktop="$xsessions_dir/i3.desktop"
# Create xsessions directory if it doesn't exist # Create xsessions directory if it doesn't exist
mkdir -p "$xsessions_dir" mkdir -p "$xsessions_dir"
# Check if i3.desktop exists, create if not # Check if i3.desktop exists, create if not
if [[ ! -f "$i3_desktop" ]]; then if [[ ! -f $i3_desktop ]]; then
cat > "$i3_desktop" << EOF cat > "$i3_desktop" << EOF
[Desktop Entry] [Desktop Entry]
Name=i3 Name=i3
Comment=improved dynamic tiling window manager Comment=improved dynamic tiling window manager
@ -199,42 +201,42 @@ X-LightDM-DesktopName=i3
DesktopNames=i3 DesktopNames=i3
Keywords=tiling;wm;windowmanager;window;manager; Keywords=tiling;wm;windowmanager;window;manager;
EOF EOF
echo "✓ Created i3 desktop session file: $i3_desktop" echo "✓ Created i3 desktop session file: $i3_desktop"
else else
echo "✓ i3 desktop session file already exists" echo "✓ i3 desktop session file already exists"
fi fi
# Ensure user has i3 config directory # Ensure user has i3 config directory
local user_home="/home/${TARGET_USER}" local user_home="/home/${TARGET_USER}"
local i3_config_dir="$user_home/.config/i3" 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" sudo -u "$TARGET_USER" mkdir -p "$i3_config_dir"
echo "✓ Created i3 config directory for user: $TARGET_USER" echo "✓ Created i3 config directory for user: $TARGET_USER"
fi fi
} }
# Function to configure additional auto-login settings # Function to configure additional auto-login settings
configure_additional_settings() { configure_additional_settings() {
echo "" echo ""
echo "4. Configuring Additional Settings..." echo "4. Configuring Additional Settings..."
echo "====================================" echo "===================================="
# Add user to autologin group if it exists # Add user to autologin group if it exists
if getent group autologin &> /dev/null; then if getent group autologin &> /dev/null; then
usermod -a -G autologin "$TARGET_USER" usermod -a -G autologin "$TARGET_USER"
echo "✓ Added $TARGET_USER to autologin group" echo "✓ Added $TARGET_USER to autologin group"
else else
# Create autologin group # Create autologin group
groupadd -r autologin groupadd -r autologin
usermod -a -G autologin "$TARGET_USER" usermod -a -G autologin "$TARGET_USER"
echo "✓ Created autologin group and added $TARGET_USER" echo "✓ Created autologin group and added $TARGET_USER"
fi fi
# Configure pam for auto-login (if needed) # Configure pam for auto-login (if needed)
local pam_lightdm="/etc/pam.d/lightdm-autologin" local pam_lightdm="/etc/pam.d/lightdm-autologin"
if [[ ! -f "$pam_lightdm" ]]; then if [[ ! -f $pam_lightdm ]]; then
cat > "$pam_lightdm" << EOF cat > "$pam_lightdm" << EOF
#%PAM-1.0 #%PAM-1.0
# LightDM auto-login PAM configuration # LightDM auto-login PAM configuration
# Created by setup_passwordless_system.sh on $(date) # Created by setup_passwordless_system.sh on $(date)
@ -247,98 +249,98 @@ password include system-local-login
session include system-local-login session include system-local-login
session optional pam_gnome_keyring.so auto_start session optional pam_gnome_keyring.so auto_start
EOF EOF
echo "✓ Created PAM configuration for auto-login" echo "✓ Created PAM configuration for auto-login"
fi fi
} }
# Function to test configurations # Function to test configurations
test_configurations() { test_configurations() {
echo "" echo ""
echo "5. Testing Configurations..." echo "5. Testing Configurations..."
echo "===========================" echo "==========================="
# Test sudo configuration # Test sudo configuration
echo "Testing passwordless sudo..." 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" echo "✓ Passwordless sudo test passed"
else else
echo "! Passwordless sudo test failed (may require logout/login)" echo "! Passwordless sudo test failed (may require logout/login)"
fi fi
# Test lightdm configuration # Test lightdm configuration
echo "Testing 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" echo "✓ LightDM configuration test passed"
else else
echo "! LightDM configuration test completed (check logs if issues occur)" echo "! LightDM configuration test completed (check logs if issues occur)"
fi fi
# Verify user is in autologin group # Verify user is in autologin group
if groups "$TARGET_USER" | grep -q autologin; then if groups "$TARGET_USER" | grep -q autologin; then
echo "✓ User is in autologin group" echo "✓ User is in autologin group"
else else
echo "! User may not be in autologin group" echo "! User may not be in autologin group"
fi fi
} }
# Function to show security warnings # Function to show security warnings
show_security_warnings() { show_security_warnings() {
echo "" echo ""
echo "⚠️ SECURITY WARNINGS ⚠️" echo "⚠️ SECURITY WARNINGS ⚠️"
echo "========================" echo "========================"
echo "" echo ""
echo "The following security changes have been made:" echo "The following security changes have been made:"
echo "" echo ""
echo "1. PASSWORDLESS SUDO:" echo "1. PASSWORDLESS SUDO:"
echo " • User '$TARGET_USER' can now run ANY command as root without password" echo " • User '$TARGET_USER' can now run ANY command as root without password"
echo " • This includes system-critical operations and file modifications" echo " • This includes system-critical operations and file modifications"
echo " • Malicious software running as this user can gain full system access" echo " • Malicious software running as this user can gain full system access"
echo "" echo ""
echo "2. AUTO-LOGIN:" echo "2. AUTO-LOGIN:"
echo " • System automatically logs in user '$TARGET_USER' on boot" echo " • System automatically logs in user '$TARGET_USER' on boot"
echo " • No password required to access the desktop environment" echo " • No password required to access the desktop environment"
echo " • Physical access to the machine = full user access" echo " • Physical access to the machine = full user access"
echo "" echo ""
echo "3. RECOMMENDATIONS:" echo "3. RECOMMENDATIONS:"
echo " • Use full disk encryption to protect against physical access" echo " • Use full disk encryption to protect against physical access"
echo " • Ensure the system is in a physically secure location" echo " • Ensure the system is in a physically secure location"
echo " • Consider using this only on personal/development machines" echo " • Consider using this only on personal/development machines"
echo " • Regularly monitor system logs for unauthorized access" echo " • Regularly monitor system logs for unauthorized access"
echo " • Keep the system updated and use a firewall" echo " • Keep the system updated and use a firewall"
echo "" echo ""
echo "4. TO DISABLE THESE SETTINGS:" echo "4. TO DISABLE THESE SETTINGS:"
echo " • Remove passwordless sudo: sudo rm /etc/sudoers.d/99-passwordless-${TARGET_USER}" echo " • Remove passwordless sudo: sudo rm /etc/sudoers.d/99-passwordless-${TARGET_USER}"
echo " • Disable auto-login: sudo rm /etc/lightdm/lightdm.conf.d/50-autologin.conf" echo " • Disable auto-login: sudo rm /etc/lightdm/lightdm.conf.d/50-autologin.conf"
echo " • Restart LightDM: sudo systemctl restart lightdm" echo " • Restart LightDM: sudo systemctl restart lightdm"
echo "" echo ""
} }
# Function to show final instructions # Function to show final instructions
show_final_instructions() { show_final_instructions() {
echo "" echo ""
echo "==========================================" echo "=========================================="
echo "Passwordless System Setup Complete" echo "Passwordless System Setup Complete"
echo "==========================================" echo "=========================================="
echo "Summary:" echo "Summary:"
echo "✓ Passwordless sudo configured for user: $TARGET_USER" echo "✓ Passwordless sudo configured for user: $TARGET_USER"
echo "✓ LightDM auto-login configured" echo "✓ LightDM auto-login configured"
echo "✓ i3 session configured" echo "✓ i3 session configured"
echo "✓ Additional auto-login settings applied" echo "✓ Additional auto-login settings applied"
echo "" echo ""
echo "Changes will take effect after:" echo "Changes will take effect after:"
echo "• Logout/login for sudo changes" echo "• Logout/login for sudo changes"
echo "• System reboot for auto-login" echo "• System reboot for auto-login"
echo "" echo ""
echo "To verify after reboot:" echo "To verify after reboot:"
echo " sudo whoami # Should not ask for password" echo " sudo whoami # Should not ask for password"
echo " systemctl status lightdm # Should show auto-login active" echo " systemctl status lightdm # Should show auto-login active"
echo "" echo ""
echo "Configuration files created:" echo "Configuration files created:"
echo " /etc/sudoers.d/99-passwordless-${TARGET_USER}" echo " /etc/sudoers.d/99-passwordless-${TARGET_USER}"
echo " /etc/lightdm/lightdm.conf.d/50-autologin.conf" echo " /etc/lightdm/lightdm.conf.d/50-autologin.conf"
echo " /etc/pam.d/lightdm-autologin" echo " /etc/pam.d/lightdm-autologin"
echo "" echo ""
echo "IMPORTANT: Reboot recommended to activate all changes!" echo "IMPORTANT: Reboot recommended to activate all changes!"
} }
# Main execution # Main execution
@ -351,22 +353,22 @@ show_security_warnings
show_final_instructions show_final_instructions
# Only offer reboot if --reboot flag was provided # Only offer reboot if --reboot flag was provided
if [[ "$OFFER_REBOOT" == true ]]; then if [[ $OFFER_REBOOT == true ]]; then
echo "" echo ""
echo "Would you like to reboot now to activate all changes?" echo "Would you like to reboot now to activate all changes?"
read -p "Reboot system now? (y/N): " -n 1 -r read -p "Reboot system now? (y/N): " -n 1 -r
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Rebooting system in 5 seconds..." echo "Rebooting system in 5 seconds..."
sleep 5 sleep 5
reboot reboot
else else
echo "Remember to reboot when convenient to activate all changes."
fi
else
echo ""
echo "Setup completed successfully."
echo "Remember to reboot when convenient to activate all changes." echo "Remember to reboot when convenient to activate all changes."
echo "To automatically prompt for reboot in the future, use: $0 --reboot" fi
else
echo ""
echo "Setup completed successfully."
echo "Remember to reboot when convenient to activate all changes."
echo "To automatically prompt for reboot in the future, use: $0 --reboot"
fi fi

View File

@ -3,19 +3,29 @@
# Function to sort files in a given directory # Function to sort files in a given directory
sort_files() { sort_files() {
local dir=$1 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 # Create directories if they do not exist
mkdir -p images videos documents mkdir -p images videos documents
# Move video files to the videos folder # 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 # 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 # Move document files to the documents folder
mv *.pdf *.doc *.docx *.txt *.odt documents/ 2>/dev/null mv -- ./*.pdf ./*.doc ./*.docx ./*.txt ./*.odt documents/ 2> /dev/null
if [[ -n $old_nullglob ]]; then
eval "$old_nullglob"
else
shopt -u nullglob
fi
} }
# Sort files in the Downloads folder # Sort files in the Downloads folder

File diff suppressed because it is too large Load Diff

View File

@ -2,47 +2,47 @@
# Check if amixer is installed, if not, install it # Check if amixer is installed, if not, install it
if ! command -v amixer &> /dev/null; then if ! command -v amixer &> /dev/null; then
echo "amixer could not be found, installing..." echo "amixer could not be found, installing..."
sudo pacman -S --noconfirm alsa-utils sudo pacman -S --noconfirm alsa-utils
fi fi
# Ensure dbus is running # Ensure dbus is running
if ! pgrep -x "dbus-daemon" > /dev/null; then if ! pgrep -x "dbus-daemon" > /dev/null; then
echo "Starting dbus..." echo "Starting dbus..."
sudo systemctl start dbus sudo systemctl start dbus
fi fi
# Ensure dbus is properly initialized for the user session # Ensure dbus is properly initialized for the user session
export $(dbus-launch) eval "$(dbus-launch)"
# Ensure notification-daemon is installed # Ensure notification-daemon is installed
if ! pacman -Qs notification-daemon > /dev/null; then if ! pacman -Qs notification-daemon > /dev/null; then
echo "Installing notification-daemon..." echo "Installing notification-daemon..."
sudo pacman -S --noconfirm notification-daemon sudo pacman -S --noconfirm notification-daemon
fi fi
# Ensure dunst is installed and running # Ensure dunst is installed and running
if ! pacman -Qs dunst > /dev/null; then if ! pacman -Qs dunst > /dev/null; then
echo "Installing dunst..." echo "Installing dunst..."
sudo pacman -S --noconfirm dunst sudo pacman -S --noconfirm dunst
fi fi
if ! pgrep -x "dunst" > /dev/null; then if ! pgrep -x "dunst" > /dev/null; then
echo "Starting dunst..." echo "Starting dunst..."
dunst & dunst &
fi fi
# Get the current state of the microphone # Get the current state of the microphone
MIC_STATE=$(amixer get Capture | grep '\[on\]') MIC_STATE=$(amixer get Capture | grep '\[on\]')
if [ -z "$MIC_STATE" ]; then if [ -z "$MIC_STATE" ]; then
# If the microphone is off, turn it on # If the microphone is off, turn it on
amixer set Capture cap amixer set Capture cap
sleep 1 # Add a delay to ensure notify-send works correctly sleep 1 # Add a delay to ensure notify-send works correctly
notify-send "Microphone" "Microphone is now ON" notify-send "Microphone" "Microphone is now ON"
else else
# If the microphone is on, turn it off # If the microphone is on, turn it off
amixer set Capture nocap amixer set Capture nocap
sleep 1 # Add a delay to ensure notify-send works correctly sleep 1 # Add a delay to ensure notify-send works correctly
notify-send "Microphone" "Microphone is now OFF" notify-send "Microphone" "Microphone is now OFF"
fi fi

View File

@ -10,48 +10,48 @@ ACTION=$1
# Check if script is run as root # Check if script is run as root
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root. Please run with sudo." echo "This script must be run as root. Please run with sudo."
exit 1 exit 1
fi fi
# Check if action parameter is provided # Check if action parameter is provided
if [[ "$ACTION" != "on" && "$ACTION" != "off" ]]; then if [[ $ACTION != "on" && $ACTION != "off" ]]; then
echo "Usage: $0 [on|off]" echo "Usage: $0 [on|off]"
exit 1 exit 1
fi fi
DEVICE_PATH="" DEVICE_PATH=""
# Find the device path in sysfs (robust scan) # Find the device path in sysfs (robust scan)
for d in /sys/bus/usb/devices/*; do for d in /sys/bus/usb/devices/*; do
if [[ -f "$d/idVendor" && -f "$d/idProduct" ]]; then if [[ -f "$d/idVendor" && -f "$d/idProduct" ]]; then
v=$(cat "$d/idVendor") v=$(cat "$d/idVendor")
p=$(cat "$d/idProduct") p=$(cat "$d/idProduct")
if [[ "$v" == "$VENDOR_ID" && "$p" == "$PRODUCT_ID" ]]; then if [[ $v == "$VENDOR_ID" && $p == "$PRODUCT_ID" ]]; then
DEVICE_PATH="$d" DEVICE_PATH="$d"
break break
fi
fi fi
fi
done done
# Check if device was found # Check if device was found
if [ -z "$DEVICE_PATH" ]; then if [ -z "$DEVICE_PATH" ]; then
echo "Device with Vendor ID $VENDOR_ID and Product ID $PRODUCT_ID not found in /sys/bus/usb/devices." echo "Device with Vendor ID $VENDOR_ID and Product ID $PRODUCT_ID not found in /sys/bus/usb/devices."
echo "Tip: Run 'lsusb | grep ${VENDOR_ID}:${PRODUCT_ID}' to verify it's connected." echo "Tip: Run 'lsusb | grep ${VENDOR_ID}:${PRODUCT_ID}' to verify it's connected."
exit 1 exit 1
fi fi
# Enable or disable the device # Enable or disable the device
if [ ! -e "$DEVICE_PATH/authorized" ]; then if [ ! -e "$DEVICE_PATH/authorized" ]; then
echo "The 'authorized' attribute is not present at $DEVICE_PATH." echo "The 'authorized' attribute is not present at $DEVICE_PATH."
echo "This device may not support toggling via 'authorized'." echo "This device may not support toggling via 'authorized'."
exit 1 exit 1
fi fi
if [ "$ACTION" == "off" ]; then if [ "$ACTION" == "off" ]; then
echo '0' > "$DEVICE_PATH/authorized" echo '0' > "$DEVICE_PATH/authorized"
echo "Device at $(basename "$DEVICE_PATH") turned off." echo "Device at $(basename "$DEVICE_PATH") turned off."
elif [ "$ACTION" == "on" ]; then elif [ "$ACTION" == "on" ]; then
echo '1' > "$DEVICE_PATH/authorized" echo '1' > "$DEVICE_PATH/authorized"
echo "Device at $(basename "$DEVICE_PATH") turned on." echo "Device at $(basename "$DEVICE_PATH") turned on."
fi fi

View File

@ -5,58 +5,61 @@ set -euo pipefail
# Configuration ----------------------------------------------------------------- # Configuration -----------------------------------------------------------------
TARGET_SESSION_NAME="Xfce Session" TARGET_SESSION_NAME="Xfce Session"
TARGET_PACKAGES=( TARGET_PACKAGES=(
xfwm4 # Compositing window manager with XFCE integration xfwm4 # Compositing window manager with XFCE integration
xfce4-session # Provides the Xfce session entry for display managers xfce4-session # Provides the Xfce session entry for display managers
xfce4-panel # Panel with system tray support xfce4-panel # Panel with system tray support
xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.) xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.)
xfce4-terminal # Handy default terminal for the new environment xfce4-terminal # Handy default terminal for the new environment
) )
# Utility functions -------------------------------------------------------------- # Utility functions --------------------------------------------------------------
info() { echo "[INFO] $*"; } info() { echo "[INFO] $*"; }
warn() { echo "[WARN] $*" >&2; } warn() { echo "[WARN] $*" >&2; }
error() { echo "[ERROR] $*" >&2; exit 1; } error() {
echo "[ERROR] $*" >&2
exit 1
}
require_command() { require_command() {
local cmd="$1" pkg_hint="${2:-}" local cmd="$1" pkg_hint="${2:-}"
if ! command -v "$cmd" >/dev/null 2>&1; then if ! command -v "$cmd" > /dev/null 2>&1; then
if [[ -n "$pkg_hint" ]]; then if [[ -n $pkg_hint ]]; then
warn "Install '$pkg_hint' to obtain the '$cmd' command." warn "Install '$pkg_hint' to obtain the '$cmd' command."
fi fi
error "Required command '$cmd' not found." error "Required command '$cmd' not found."
fi fi
} }
ensure_pacman() { ensure_pacman() {
require_command pacman "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." warn "This script was designed for Arch Linux; continuing anyway."
fi fi
} }
install_packages() { install_packages() {
local missing=() local missing=()
for pkg in "${TARGET_PACKAGES[@]}"; do 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") missing+=("$pkg")
fi fi
done done
if [[ ${#missing[@]} -eq 0 ]]; then if [[ ${#missing[@]} -eq 0 ]]; then
info "All target packages are already installed." info "All target packages are already installed."
return return
fi 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." error "sudo is required to install packages. Install sudo or run this script as root."
fi fi
info "Installing missing packages: ${missing[*]}" info "Installing missing packages: ${missing[*]}"
sudo pacman -S --needed --noconfirm "${missing[@]}" sudo pacman -S --needed --noconfirm "${missing[@]}"
} }
print_post_install_tips() { print_post_install_tips() {
cat <<EOF cat << EOF
------------------------------------------------------------------------ ------------------------------------------------------------------------
XFCE session installed. XFCE session installed.
@ -73,31 +76,31 @@ EOF
} }
logout_user() { logout_user() {
local session_id="${XDG_SESSION_ID:-}" 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." info "Terminating current session (ID: $session_id) via loginctl."
loginctl terminate-session "$session_id" loginctl terminate-session "$session_id"
return return
fi 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." info "Terminating all sessions for user '$USER' via loginctl."
loginctl terminate-user "$USER" loginctl terminate-user "$USER"
return return
fi fi
warn "loginctl could not terminate the session; attempting fallback logout." warn "loginctl could not terminate the session; attempting fallback logout."
pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually." pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually."
} }
main() { main() {
ensure_pacman ensure_pacman
install_packages install_packages
print_post_install_tips print_post_install_tips
# Give the user a moment to read the instructions before logging out. # Give the user a moment to read the instructions before logging out.
logout_user logout_user
} }
main "$@" main "$@"

View File

@ -22,20 +22,20 @@ set -euo pipefail
log() { printf "[idle-off] %s\n" "$*"; } log() { printf "[idle-off] %s\n" "$*"; }
warn() { printf "[idle-off][WARN] %s\n" "$*" >&2; } 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 persist_systemd=false
watch_controller=false watch_controller=false
for arg in "${@:-}"; do for arg in "${@:-}"; do
case "$arg" in case "$arg" in
--persist-systemd) --persist-systemd)
persist_systemd=true persist_systemd=true
;; ;;
--watch-controller) --watch-controller)
watch_controller=true watch_controller=true
;; ;;
-h|--help) -h | --help)
cat <<EOF cat << EOF
Usage: $(basename "$0") [--persist-systemd] [--watch-controller] Usage: $(basename "$0") [--persist-systemd] [--watch-controller]
Disables idle detection, screen blanking, and auto-lock for the current session. Disables idle detection, screen blanking, and auto-lock for the current session.
@ -54,173 +54,172 @@ What this does:
- Optional: systemd-logind IdleAction=ignore - Optional: systemd-logind IdleAction=ignore
- Optional: watch controller input and reset idle timers - Optional: watch controller input and reset idle timers
EOF EOF
exit 0 exit 0
;; ;;
esac esac
done done
disable_x11_idle() { 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" log "Disabling X11 DPMS/screensaver/blanking via xset"
xset -dpms || true xset -dpms || true
xset s off || true xset s off || true
xset s noblank || true xset s noblank || true
else else
log "X11/xset not detected or DISPLAY not set; skipping xset" log "X11/xset not detected or DISPLAY not set; skipping xset"
fi fi
} }
disable_gnome_idle() { disable_gnome_idle() {
if has_cmd gsettings; then if has_cmd gsettings; then
# Detect GNOME by presence of GNOME schemas # 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" log "Applying GNOME settings to disable idle and lock"
# No lock on idle # 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) # 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 # 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-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-battery-type 'nothing' 2> /dev/null || true
# Optional: ensure screensaver idle-activation-enabled is false (for older setups) # 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
fi fi
} }
disable_kde_idle() { disable_kde_idle() {
# Best-effort: turn off auto-locker; note: Plasma on Wayland still may rely on compositor-level settings # Best-effort: turn off auto-locker; note: Plasma on Wayland still may rely on compositor-level settings
if has_cmd kwriteconfig5; then if has_cmd kwriteconfig5; then
log "Disabling KDE Plasma screen auto-lock (kscreenlockerrc)" 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 Autolock false 2> /dev/null || true
kwriteconfig5 --file kscreenlockerrc --group Daemon --key LockOnResume 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 Timeout 0 2> /dev/null || true
fi fi
} }
disable_sway_idle() { disable_sway_idle() {
# Sway commonly uses swayidle for idle actions; killing it prevents screen blanking/locking # 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 sway > /dev/null 2>&1; then
if pgrep -x swayidle >/dev/null 2>&1; then if pgrep -x swayidle > /dev/null 2>&1; then
log "Killing swayidle to prevent Wayland idle actions" log "Killing swayidle to prevent Wayland idle actions"
pkill -x swayidle || true pkill -x swayidle || true
fi fi
fi fi
} }
disable_lock_daemons() { disable_lock_daemons() {
# Stop common screen lockers/idle helpers if running # Stop common screen lockers/idle helpers if running
local daemons=(xss-lock light-locker xscreensaver gnome-screensaver) local daemons=(xss-lock light-locker xscreensaver gnome-screensaver)
local found=false local found=false
for d in "${daemons[@]}"; do 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 found=true
log "Stopping ${d}" log "Stopping ${d}"
pkill -x "$d" || true pkill -x "$d" || true
fi fi
done done
if [[ "$found" == false ]]; then if [[ $found == false ]]; then
log "No known lock daemons running" log "No known lock daemons running"
fi fi
} }
disable_tty_idle() { disable_tty_idle() {
if has_cmd setterm; then if has_cmd setterm; then
log "Disabling TTY blanking and powersave" log "Disabling TTY blanking and powersave"
# Apply to the current TTY; also attempt to broadcast to common TTYs # Apply to the current TTY; also attempt to broadcast to common TTYs
setterm -blank 0 -powersave off -powerdown 0 || true setterm -blank 0 -powersave off -powerdown 0 || true
for tty in /dev/tty{1..12}; do for tty in /dev/tty{1..12}; do
[[ -e "$tty" ]] || continue [[ -e $tty ]] || continue
setterm -blank 0 -powersave off -powerdown 0 <"$tty" >/dev/null 2>&1 || true setterm -blank 0 -powersave off -powerdown 0 < "$tty" > /dev/null 2>&1 || true
done done
fi fi
} }
reset_idle_activity() { reset_idle_activity() {
# Trigger activity hints depending on environment # Trigger activity hints depending on environment
if [[ -n "${DISPLAY:-}" ]]; then if [[ -n ${DISPLAY:-} ]]; then
if has_cmd xset; then if has_cmd xset; then
xset s reset || true xset s reset || true
xset -dpms || true xset -dpms || true
xset s off || true xset s off || true
xset s noblank || true xset s noblank || true
fi fi
if has_cmd xdotool; then if has_cmd xdotool; then
# No-op mousemove to generate X11 activity without visible movement # 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
fi fi
} }
watch_js_device() { watch_js_device() {
local dev="$1" local dev="$1"
log "Watching controller device: $dev" log "Watching controller device: $dev"
while :; do while :; do
if [[ ! -e "$dev" ]]; then if [[ ! -e $dev ]]; then
warn "Device disappeared: $dev" warn "Device disappeared: $dev"
break break
fi fi
# Joystick API event size is 8 bytes; block until an event arrives # Joystick API event size is 8 bytes; block until an event arrives
if dd if="$dev" bs=8 count=1 status=none of=/dev/null; then if dd if="$dev" bs=8 count=1 status=none of=/dev/null; then
reset_idle_activity reset_idle_activity
# Debounce bursts of events # Debounce bursts of events
sleep 0.3 sleep 0.3
else else
# On read error (e.g., permission), backoff # On read error (e.g., permission), backoff
sleep 1 sleep 1
fi fi
done done
} }
start_controller_watchers() { start_controller_watchers() {
# Attempt to watch all /dev/input/js* devices; rescan periodically for new ones # Attempt to watch all /dev/input/js* devices; rescan periodically for new ones
local seen="" declare -A pids
declare -A pids
# Initial permission check # Initial permission check
local any_js=false any_readable=false local any_js=false any_readable=false
for dev in /dev/input/js*; do for dev in /dev/input/js*; do
[[ -e "$dev" ]] || continue [[ -e $dev ]] || continue
any_js=true any_js=true
if [[ -r "$dev" ]]; then any_readable=true; fi if [[ -r $dev ]]; then any_readable=true; fi
done 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." warn "No read permission to /dev/input/js*; add your user to the 'input' group or create udev rules."
fi fi
while :; do while :; do
local found_any=false local found_any=false
for dev in /dev/input/js*; do for dev in /dev/input/js*; do
[[ -e "$dev" ]] || continue [[ -e $dev ]] || continue
found_any=true 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 # Start a watcher for this device in background
watch_js_device "$dev" & watch_js_device "$dev" &
pids[$dev]=$! pids[$dev]=$!
fi fi
done done
if [[ "$found_any" == false ]]; then if [[ $found_any == false ]]; then
# No joystick devices; quiet rescan # No joystick devices; quiet rescan
sleep 5 sleep 5
else else
# Rescan less frequently when active # Rescan less frequently when active
sleep 2 sleep 2
fi fi
done done
} }
persist_with_systemd_logind() { persist_with_systemd_logind() {
# Set IdleAction=ignore in /etc/systemd/logind.conf and restart 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. # 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 return 0
fi fi
if ! has_cmd sudo; then if ! has_cmd sudo; then
warn "sudo not found; cannot persist systemd-logind setting" warn "sudo not found; cannot persist systemd-logind setting"
return 0 return 0
fi fi
log "Persisting: setting systemd-logind IdleAction=ignore (requires sudo)" log "Persisting: setting systemd-logind IdleAction=ignore (requires sudo)"
sudo sh -c ' sudo sh -c '
set -e set -e
conf=/etc/systemd/logind.conf conf=/etc/systemd/logind.conf
if [ ! -f "$conf" ]; then if [ ! -f "$conf" ]; then
@ -235,39 +234,38 @@ persist_with_systemd_logind() {
printf "\nIdleAction=ignore\n" >> "$conf" printf "\nIdleAction=ignore\n" >> "$conf"
fi fi
' '
log "Restarting systemd-logind to apply changes (may briefly affect session inhibitors)" log "Restarting systemd-logind to apply changes (may briefly affect session inhibitors)"
sudo systemctl restart systemd-logind || warn "Failed to restart systemd-logind" sudo systemctl restart systemd-logind || warn "Failed to restart systemd-logind"
} }
main() { main() {
log "Starting idle/lock disablement" log "Starting idle/lock disablement"
# Environment-aware steps # Environment-aware steps
disable_x11_idle disable_x11_idle
disable_gnome_idle disable_gnome_idle
disable_kde_idle disable_kde_idle
disable_sway_idle disable_sway_idle
# Generic steps # Generic steps
disable_lock_daemons disable_lock_daemons
disable_tty_idle disable_tty_idle
# Optional persistence # Optional persistence
persist_with_systemd_logind persist_with_systemd_logind
if [[ "$watch_controller" == true ]]; then if [[ $watch_controller == true ]]; then
log "Controller activity watcher enabled" log "Controller activity watcher enabled"
# Keep the script alive to watch controllers # Keep the script alive to watch controllers
start_controller_watchers & start_controller_watchers &
watcher_pid=$! watcher_pid=$!
log "Watcher PID: $watcher_pid" log "Watcher PID: $watcher_pid"
# Wait indefinitely and forward termination # Wait indefinitely and forward termination
trap 'log "Stopping controller watcher"; kill "$watcher_pid" 2>/dev/null || true; exit 0' INT TERM trap 'log "Stopping controller watcher"; kill "$watcher_pid" 2>/dev/null || true; exit 0' INT TERM
wait "$watcher_pid" wait "$watcher_pid"
else else
log "Done. The screen should no longer blank, lock, or power down automatically." log "Done. The screen should no longer blank, lock, or power down automatically."
fi fi
} }
main "$@" main "$@"