From 3a62a02c3dc51d99fd92bc7438d5aa0e05209a87 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Thu, 11 Dec 2025 18:32:15 +0100 Subject: [PATCH] refactor: reduce code duplication from 1.97% to 0.76% - Add common.sh library functions: require_imagemagick, install_missing_pacman_packages, handle_arg_help_or_unknown - Create android.sh shared library for Android utilities - Create hosts-guard-common.sh for pacman hooks shared functions - Update multiple scripts to source common.sh and use shared helpers - Add print_shutdown_schedule helper in setup_midnight_shutdown.sh - Remove duplicate log(), usage(), install_packages patterns across scripts - Format all shell scripts with shfmt (2-space indent) --- .githooks/pre-commit | 2 +- fresh-install/main.sh | 399 ++-- .../guard/pacman-hooks/hosts-guard-common.sh | 91 + .../pacman-hooks/pacman-post-relock-hosts.sh | 52 +- .../pacman-hooks/pacman-pre-unlock-hosts.sh | 68 +- hosts/install.sh | 8 +- report/jscpd-report.json | 1793 +++++++++++++++++ scripts/check_and_enable_services.sh | 154 +- .../block_compulsive_opening.sh | 26 +- .../digital_wellbeing/music_parallelism.sh | 6 +- .../pacman/pacman_wrapper.sh | 540 ++--- .../setup_midnight_shutdown.sh | 28 +- scripts/features/install_unreal_mcp.sh | 13 +- scripts/features/raspberry_pi_flash_sd.sh | 50 +- scripts/features/raspberry_pi_nextcloud.sh | 44 +- scripts/features/setup_activitywatch.sh | 572 +++--- scripts/features/setup_nextcloud_raspberry.sh | 76 +- scripts/fixes/fix_virtualbox.sh | 281 ++- scripts/fixes/nvidia_troubleshoot.sh | 11 +- scripts/lib/android.sh | 50 + scripts/lib/common.sh | 269 ++- scripts/meta/shell_check.sh | 625 +++--- .../testsAndMisc-bash/fix_thorium_unity.sh | 169 +- scripts/misc/testsAndMisc-bash/fix_unity.sh | 399 ++-- .../testsAndMisc-bash/get_rnnoise_model.sh | 235 +-- .../install_ffmpeg_with_arnndn.sh | 211 +- .../testsAndMisc-bash/install_unity_mcp.sh | 264 ++- scripts/setup_periodic_system.sh | 11 +- scripts/setup_thorium_startup.sh | 11 +- scripts/utils/convert_video.sh | 41 +- scripts/utils/image_to_resolution.sh | 65 +- scripts/utils/pdf_to_image.sh | 122 +- scripts/utils/root_bl9000.sh | 6 +- scripts/utils/setup_android_adblock.sh | 38 +- scripts/utils/toggle_window_manager.sh | 94 +- scripts/utils/txt_to_image.sh | 125 +- scripts/utils/update_android_hosts.sh | 22 +- 37 files changed, 4355 insertions(+), 2616 deletions(-) create mode 100644 hosts/guard/pacman-hooks/hosts-guard-common.sh create mode 100644 report/jscpd-report.json create mode 100644 scripts/lib/android.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2772978..4e9d1b5 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -61,7 +61,7 @@ printf 'Running duplicate code detection...\n' JSCPD_BIN="${HOME}/.local/node_modules/.bin/jscpd" # Install jscpd if not present -if [[ ! -x "$JSCPD_BIN" ]]; then +if [[ ! -x $JSCPD_BIN ]]; then printf ' → jscpd not found, installing...\n' if ! npm install --prefix ~/.local jscpd 2>&1; then printf '\nCommit aborted: failed to install jscpd.\n' >&2 diff --git a/fresh-install/main.sh b/fresh-install/main.sh index 9dec655..4132421 100755 --- a/fresh-install/main.sh +++ b/fresh-install/main.sh @@ -5,11 +5,11 @@ set -e # Function to play a sound on error play_error_sound() { - #pactl set-sink-volume @DEFAULT_SINK@ +50% - for _ in 1 2 3; do - paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga - done - #pactl set-sink-volume @DEFAULT_SINK@ -50% + #pactl set-sink-volume @DEFAULT_SINK@ +50% + for _ in 1 2 3; do + paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga + done + #pactl set-sink-volume @DEFAULT_SINK@ -50% } # Trap errors and call the play_error_sound function @@ -20,105 +20,108 @@ git config --global init.defaultBranch main # GPU detection (now split vendor-specific logic) if [ -f "./detect_gpu.sh" ]; then - # shellcheck source=./detect_gpu.sh disable=SC1091 - . ./detect_gpu.sh + # shellcheck source=./detect_gpu.sh disable=SC1091 + . ./detect_gpu.sh elif [ -f "./detect_gpu_and_install.sh" ]; then - # shellcheck source=./detect_gpu_and_install.sh disable=SC1091 - . ./detect_gpu_and_install.sh + # shellcheck source=./detect_gpu_and_install.sh disable=SC1091 + . ./detect_gpu_and_install.sh else - echo "GPU detection scripts not found; continuing without GPU specific installation." + echo "GPU detection scripts not found; continuing without GPU specific installation." fi install_from_aur() { - local repo_url pkg_name repo_dir - repo_url="$1" - pkg_name="$2" + local repo_url pkg_name repo_dir + repo_url="$1" + pkg_name="$2" - mkdir -p "$HOME/aur" - cd "$HOME/aur" || return 1 - repo_dir="$(basename "$repo_url" .git)" + mkdir -p "$HOME/aur" + cd "$HOME/aur" || return 1 + repo_dir="$(basename "$repo_url" .git)" - if [ ! -d "$repo_dir" ]; then - git clone "$repo_url" - else - echo "Repository $repo_dir already cloned; updating" - (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) - fi - cd "$repo_dir" || return 1 + if [ ! -d "$repo_dir" ]; then + git clone "$repo_url" + else + echo "Repository $repo_dir already cloned; updating" + (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) + fi + cd "$repo_dir" || return 1 - if pacman -Qi "$pkg_name" > /dev/null 2>&1; then - echo "$pkg_name is already installed" - return 0 - fi + if pacman -Qi "$pkg_name" >/dev/null 2>&1; then + echo "$pkg_name is already installed" + return 0 + fi - echo "Cleaning old package artifacts to avoid duplicate -U targets" - find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true + echo "Cleaning old package artifacts to avoid duplicate -U targets" + find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true - echo "Building $pkg_name (clean build)" - # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) - if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then - echo "Build failed for $pkg_name" >&2 - return 1 - fi + echo "Building $pkg_name (clean build)" + # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) + if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then + echo "Build failed for $pkg_name" >&2 + return 1 + fi - # Collect only the freshly built packages (should now be only current version) - mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') - if [ ${#built_pkgs[@]} -eq 0 ]; then - echo "No package files produced for $pkg_name" >&2 - return 1 - fi + # Collect only the freshly built packages (should now be only current version) + mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') + if [ ${#built_pkgs[@]} -eq 0 ]; then + echo "No package files produced for $pkg_name" >&2 + return 1 + fi - echo "Installing built package(s): ${built_pkgs[*]}" - if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then - echo "Installation failed for $pkg_name" >&2 - return 1 - fi + 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 +} + +# Helper: try to install from AUR and log result to done.txt/failed.txt +try_aur_install() { + local repo_url="$1" + local pkg_name="$2" + if install_from_aur "$repo_url" "$pkg_name"; then + echo "$pkg_name" >>done.txt + else + echo "$pkg_name" >>failed.txt + fi } process_packages() { - local file_path - file_path="$1" - : > failed.txt - : > done.txt + local file_path + file_path="$1" + : >failed.txt + : >done.txt - while IFS= read -r pkg_name; do - if [ -z "$pkg_name" ]; then - continue - fi + while IFS= read -r pkg_name; do + if [ -z "$pkg_name" ]; then + continue + fi - local repo_url repo_dir - repo_url="https://aur.archlinux.org/${pkg_name}-git.git" - repo_dir="${pkg_name}-git" + local repo_url repo_dir + repo_url="https://aur.archlinux.org/${pkg_name}-git.git" + repo_dir="${pkg_name}-git" - git clone "$repo_url" - if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then - echo "Repository $repo_dir is empty, trying without -git suffix" - repo_url="https://aur.archlinux.org/${pkg_name}.git" - repo_dir="${pkg_name}" + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying without -git suffix" + repo_url="https://aur.archlinux.org/${pkg_name}.git" + repo_dir="${pkg_name}" - git clone "$repo_url" - if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then - echo "Repository $repo_dir is empty, trying to install with pacman" - if sudo pacman -Sy --noconfirm "$pkg_name"; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi - else - if install_from_aur "$repo_url" "$pkg_name"; then - echo "$pkg_name" >> done.txt - else - echo "$pkg_name" >> failed.txt - fi - fi - 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" + git clone "$repo_url" + if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then + echo "Repository $repo_dir is empty, trying to install with pacman" + if sudo pacman -Sy --noconfirm "$pkg_name"; then + echo "$pkg_name" >>done.txt + else + echo "$pkg_name" >>failed.txt + fi + else + try_aur_install "$repo_url" "$pkg_name" + fi + else + try_aur_install "$repo_url" "$pkg_name" + fi + done <"$file_path" } sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak @@ -129,161 +132,161 @@ sudo cp ./pacman.conf /etc/pacman.conf # sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf # mkinitcpio -P # Reflector install / service management (idempotent & resilient) -if pacman -Qi reflector > /dev/null 2>&1; then - echo "reflector already installed" +if pacman -Qi reflector >/dev/null 2>&1; then + echo "reflector already installed" else - yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)" + yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)" fi # Prefer timer over service (Arch default) if systemctl list-unit-files | grep -q '^reflector.timer'; then - if systemctl is-enabled reflector.timer > /dev/null 2>&1; then - echo "reflector.timer already enabled" - else - sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer" - fi - if systemctl is-active reflector.timer > /dev/null 2>&1; then - echo "reflector.timer already active" - else - if ! sudo systemctl start reflector.timer; then - echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)" - fi - fi + if systemctl is-enabled reflector.timer >/dev/null 2>&1; then + echo "reflector.timer already enabled" + else + sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer" + fi + if systemctl is-active reflector.timer >/dev/null 2>&1; then + echo "reflector.timer already active" + else + if ! sudo systemctl start reflector.timer; then + echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)" + fi + fi elif systemctl list-unit-files | grep -q '^reflector.service'; then - if systemctl is-enabled reflector.service > /dev/null 2>&1; then - echo "reflector.service already enabled" - else - sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service" - fi - if systemctl is-active reflector.service > /dev/null 2>&1; then - echo "reflector.service already running" - else - if ! sudo systemctl start reflector.service; then - echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)" - fi - fi + if systemctl is-enabled reflector.service >/dev/null 2>&1; then + echo "reflector.service already enabled" + else + sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service" + fi + if systemctl is-active reflector.service >/dev/null 2>&1; then + echo "reflector.service already running" + else + if ! sudo systemctl start reflector.service; then + echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)" + fi + fi else - echo "reflector systemd unit not found (neither timer nor service)" + echo "reflector systemd unit not found (neither timer nor service)" fi # Read AUR packages from file (needed before pacman processing) declare -a aur_packages=() declare -a aur_package_names=() while IFS= read -r line; do - if [[ -n $line && $line =~ ^[a-z0-9] ]]; then - aur_packages+=("$line") - aur_package_names+=("${line%% *}") - fi -done < "aur_packages.txt" + if [[ -n $line && $line =~ ^[a-z0-9] ]]; then + aur_packages+=("$line") + aur_package_names+=("${line%% *}") + fi +done <"aur_packages.txt" + +# Helper: Check if all subpackages are installed +# Returns 0 if ALL subpackages are installed, 1 otherwise +all_subpackages_installed() { + local -n sub_pkgs_ref=$1 + for subpkg in "${sub_pkgs_ref[@]}"; do + if ! pacman -Qi "$subpkg" &>/dev/null; then + return 1 + fi + done + return 0 +} # Read pacman packages from file declare -a pacman_packages while IFS= read -r line; do - # Skip empty lines and comments (lines not starting with alphanumeric characters) - if [[ -n $line && $line =~ ^[a-z0-9] ]]; then - pacman_packages+=("$line") - fi -done < "pacman_packages.txt" + # Skip empty lines and comments (lines not starting with alphanumeric characters) + if [[ -n $line && $line =~ ^[a-z0-9] ]]; then + pacman_packages+=("$line") + fi +done <"pacman_packages.txt" for pkg in "${pacman_packages[@]}"; do - # Skip NVIDIA packages if GPU is not NVIDIA - if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then - echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)" - continue - fi - # Check for texlive subpackages - if [ "$pkg" == "texlive" ]; then - sub_pkgs=( - texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra - texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities - texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience - texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks - texlive-publishers texlive-xetex - ) - all_installed=true - for subpkg in "${sub_pkgs[@]}"; do - if ! pacman -Qi "$subpkg" &> /dev/null; then - all_installed=false - break - fi - done - if [ "$all_installed" = true ]; then - echo "All texlive subpackages are installed, skipping texlive" - continue - fi - fi + # Skip NVIDIA packages if GPU is not NVIDIA + if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then + echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)" + continue + fi + # Check for texlive subpackages + if [ "$pkg" == "texlive" ]; then + # shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed + texlive_sub_pkgs=( + texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra + texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities + texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience + texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks + texlive-publishers texlive-xetex + ) + if all_subpackages_installed texlive_sub_pkgs; then + echo "All texlive subpackages are installed, skipping texlive" + continue + fi + fi - # Check for texlive-lang subpackages - if [ "$pkg" == "texlive-lang" ]; then - sub_pkgs=( - texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic - texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench - texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese - texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese - texlive-langspanish - ) - all_installed=true - for subpkg in "${sub_pkgs[@]}"; do - if ! pacman -Qi "$subpkg" &> /dev/null; then - all_installed=false - break - fi - done - if [ "$all_installed" = true ]; then - echo "All texlive-lang subpackages are installed, skipping texlive-lang" - continue - fi - fi + # Check for texlive-lang subpackages + if [ "$pkg" == "texlive-lang" ]; then + # shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed + texlive_lang_sub_pkgs=( + texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic + texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench + texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese + texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese + texlive-langspanish + ) + if all_subpackages_installed texlive_lang_sub_pkgs; then + echo "All texlive-lang subpackages are installed, skipping texlive-lang" + continue + fi + fi - if ! pacman -Qi "$pkg" &> /dev/null; then - if ! printf '%s + if ! pacman -Qi "$pkg" &>/dev/null; then + if ! printf '%s ' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then - yes | sudo pacman -Sy --noconfirm "$pkg" - else - echo "$pkg exists in AUR packages, skipping pacman installation" - fi - else - echo "$pkg is already installed" - fi + yes | sudo pacman -Sy --noconfirm "$pkg" + else + echo "$pkg exists in AUR packages, skipping pacman installation" + fi + else + echo "$pkg is already installed" + fi done -if ! command -v nvm &> /dev/null; then - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash +if ! command -v nvm &>/dev/null; then + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash else - echo "nvm is already installed" + echo "nvm is already installed" fi export NVM_DIR="$HOME/.nvm" if [ -s "$NVM_DIR/nvm.sh" ]; then - # shellcheck source=/dev/null - . "$NVM_DIR/nvm.sh" + # shellcheck source=/dev/null + . "$NVM_DIR/nvm.sh" else - echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2 + 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 +if command -v nvm &>/dev/null; then + nvm i v18.20.5 + nvm install --lts else - echo "nvm command unavailable; skipping Node installation" >&2 + echo "nvm command unavailable; skipping Node installation" >&2 fi sudo systemctl enable bluetooth.service sudo systemctl start bluetooth.service for entry in "${aur_packages[@]}"; do - pkg_name=${entry%% *} - repo_url=${entry#* } - if [ "$repo_url" = "$pkg_name" ] || [ -z "$repo_url" ]; then - repo_url="https://aur.archlinux.org/${pkg_name}.git" - fi - install_from_aur "$repo_url" "$pkg_name" + pkg_name=${entry%% *} + repo_url=${entry#* } + if [ "$repo_url" = "$pkg_name" ] || [ -z "$repo_url" ]; then + repo_url="https://aur.archlinux.org/${pkg_name}.git" + fi + install_from_aur "$repo_url" "$pkg_name" done cd ~/linux-configuration/fresh-install if [ ! -d "$HOME/.config/mpv" ]; then - mkdir -p "$HOME/.config/mpv" + mkdir -p "$HOME/.config/mpv" fi cp mpv.conf "$HOME/.config/mpv/mpv.conf" if [ ! -d "$HOME/.oh-my-zsh" ]; then - yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" + yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" else - echo "Oh My Zsh is already installed" + echo "Oh My Zsh is already installed" fi cd ~/linux-configuration diff --git a/hosts/guard/pacman-hooks/hosts-guard-common.sh b/hosts/guard/pacman-hooks/hosts-guard-common.sh new file mode 100644 index 0000000..9872cc3 --- /dev/null +++ b/hosts/guard/pacman-hooks/hosts-guard-common.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# Shared functions for hosts-guard pacman hooks +# This file is sourced by pacman-pre-unlock-hosts.sh and pacman-post-relock-hosts.sh + +TARGET=/etc/hosts +LOGTAG=hosts-guard-hook + +# Check if target has a read-only mount +is_ro_mount() { + findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro +} + +# Count mount layers for the target +mount_layers_count() { + awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0 +} + +# Collapse all bind mount layers +collapse_mounts() { + local i=0 + if command -v mountpoint >/dev/null 2>&1; then + while mountpoint -q "$TARGET"; do + umount -l "$TARGET" >/dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break + done + else + local cnt + cnt=$(mount_layers_count) + while ((cnt > 1)); do + umount -l "$TARGET" >/dev/null 2>&1 || break + i=$((i + 1)) + ((i > 20)) && break + cnt=$(mount_layers_count) + done + fi +} + +# Stop systemd units related to hosts guard +stop_units_if_present() { + local units=(hosts-bind-mount.service hosts-guard.path) + for u in "${units[@]}"; do + if command -v systemctl >/dev/null 2>&1; then + if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then + systemctl stop "$u" >/dev/null 2>&1 || true + fi + fi + done +} + +# Remove immutable/append-only attributes +remove_host_attrs() { + if command -v lsattr >/dev/null 2>&1; then + local attrs + attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) + if echo "$attrs" | grep -q " i "; then + chattr -i "$TARGET" >/dev/null 2>&1 || true + fi + if echo "$attrs" | grep -q " a "; then + chattr -a "$TARGET" >/dev/null 2>&1 || true + fi + fi +} + +# Apply immutable attribute +apply_immutable() { + if command -v chattr >/dev/null 2>&1; then + chattr +i "$TARGET" >/dev/null 2>&1 || true + fi +} + +# Apply a single read-only bind mount layer +apply_ro_bind_mount() { + mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true + mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true +} + +# Start the path watcher service +start_path_watcher() { + if command -v systemctl >/dev/null 2>&1; then + systemctl start hosts-guard.path >/dev/null 2>&1 || true + fi +} + +# Log to system logger and run log file +log_hook() { + local phase="$1" + local state="$2" + logger -t "$LOGTAG" "$phase: $state" + echo "$(date -Is) $phase-$state" >>/run/hosts-guard-hook.log 2>/dev/null || true +} diff --git a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh index abc0bf0..9dc25ca 100644 --- a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh +++ b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh @@ -3,54 +3,30 @@ set -euo pipefail -TARGET=/etc/hosts +# Source shared functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=hosts-guard-common.sh +source "$SCRIPT_DIR/hosts-guard-common.sh" + ENFORCE=/usr/local/sbin/enforce-hosts.sh -LOGTAG=hosts-guard-hook -mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } -collapse_mounts() { - local i=0 - if command -v mountpoint >/dev/null 2>&1; then - while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i + 1)) - ((i > 20)) && break - done - else - local cnt - cnt=$(mount_layers_count) - while ((cnt > 1)); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i + 1)) - ((i > 20)) && break - cnt=$(mount_layers_count) - done - fi -} +log_hook "post" "relocking(start)" -# Ensure we end with a single read-only bind mount layer -logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)" -echo "$(date -Is) post-relock(start)" >>/run/hosts-guard-hook.log 2>/dev/null || true +# Collapse any stacked mounts first collapse_mounts +# Run enforcement script if available if [[ -x $ENFORCE ]]; then "$ENFORCE" >/dev/null 2>&1 || true fi -if command -v chattr >/dev/null 2>&1; then - chattr +i "$TARGET" >/dev/null 2>&1 || true -fi +# Apply protections +apply_immutable +apply_ro_bind_mount -# Apply exactly one ro bind layer -mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true -mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true +# Start the path watcher +start_path_watcher -# Start only the path watcher; avoid bind-mount service (we already bound once) -if command -v systemctl >/dev/null 2>&1; then - systemctl start hosts-guard.path >/dev/null 2>&1 || true -fi - -logger -t "$LOGTAG" "post: relocking /etc/hosts (done)" -echo "$(date -Is) post-relock(done)" >>/run/hosts-guard-hook.log 2>/dev/null || true +log_hook "post" "relocking(done)" exit 0 diff --git a/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh index e407961..d6cfdf2 100644 --- a/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh +++ b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh @@ -3,69 +3,27 @@ set -euo pipefail -TARGET=/etc/hosts -LOGTAG=hosts-guard-hook +# Source shared functions +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=hosts-guard-common.sh +source "$SCRIPT_DIR/hosts-guard-common.sh" -stop_units_if_present() { - local units=(hosts-bind-mount.service hosts-guard.path) - for u in "${units[@]}"; do - if command -v systemctl >/dev/null 2>&1; then - if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then - systemctl stop "$u" >/dev/null 2>&1 || true - fi - fi - done -} - -is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; } - -mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } -cleanup_mount_stacks() { - local i=0 - # Unmount bind layers until /etc/hosts is no longer a mountpoint - if command -v mountpoint >/dev/null 2>&1; then - while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i + 1)) - ((i > 20)) && break - done - else - # Fallback to best-effort using mountinfo count - local cnt - cnt=$(mount_layers_count) - while ((cnt > 1)); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i + 1)) - ((i > 20)) && break - cnt=$(mount_layers_count) - done - fi -} - -# Drop protective attributes if present -if command -v lsattr >/dev/null 2>&1; then - attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) - if echo "$attrs" | grep -q " i "; then - chattr -i "$TARGET" >/dev/null 2>&1 || true - fi - if echo "$attrs" | grep -q " a "; then - chattr -a "$TARGET" >/dev/null 2>&1 || true - fi -fi +# Remove protective attributes +remove_host_attrs +# Stop guard services stop_units_if_present -logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)" -echo "$(date -Is) pre-unlock" >>/run/hosts-guard-hook.log 2>/dev/null || true +log_hook "pre" "unlocking(start)" -# Always collapse any existing layers; we'll operate on the plain file -cleanup_mount_stacks +# Collapse any existing mount layers +collapse_mounts -# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again +# Ensure writable by remounting if still read-only 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 || collapse_mounts fi -logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)" +log_hook "pre" "unlocking(done)" exit 0 diff --git a/hosts/install.sh b/hosts/install.sh index 74eb331..1628e34 100755 --- a/hosts/install.sh +++ b/hosts/install.sh @@ -50,7 +50,7 @@ extract_custom_entries_from_script() { # Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker) extract_custom_entries_from_hosts() { local hosts_file="$1" - if [[ ! -f "$hosts_file" ]]; then + if [[ ! -f $hosts_file ]]; then return fi sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" | @@ -61,7 +61,7 @@ extract_custom_entries_from_hosts() { # Load previously saved custom entries state load_saved_custom_entries() { - if [[ -f "$CUSTOM_ENTRIES_STATE_FILE" ]]; then + if [[ -f $CUSTOM_ENTRIES_STATE_FILE ]]; then sort -u "$CUSTOM_ENTRIES_STATE_FILE" fi } @@ -77,7 +77,7 @@ save_custom_entries_state() { # Helper function to count non-empty lines count_lines() { local input="$1" - if [[ -z "$input" ]]; then + if [[ -z $input ]]; then echo 0 else echo "$input" | grep -c . 2>/dev/null || echo 0 @@ -98,7 +98,7 @@ check_custom_entries_protection() { # Get saved/existing entries (prefer state file, fall back to current /etc/hosts) local saved_entries saved_entries=$(load_saved_custom_entries) - if [[ -z "$saved_entries" ]]; then + if [[ -z $saved_entries ]]; then # First run or state file missing - extract from current /etc/hosts if it has our marker saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts") fi diff --git a/report/jscpd-report.json b/report/jscpd-report.json new file mode 100644 index 0000000..a40f27c --- /dev/null +++ b/report/jscpd-report.json @@ -0,0 +1,1793 @@ +{ + "statistics": { + "detectionDate": "2025-12-11T17:15:21.387Z", + "formats": { + "python": { + "sources": { + "scripts/misc/testsAndMisc-bash/tools/transcribe_fw.py": { + "lines": 395, + "tokens": 4125, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 395, + "tokens": 4125, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "json": { + "sources": { + "scripts/misc/testsAndMisc-bash/.vscode/tasks.json": { + "lines": 20, + "tokens": 96, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 20, + "tokens": 96, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "bash": { + "sources": { + "scripts/system-maintenance/bin/shutdown-timer-monitor.sh": { + "lines": 130, + "tokens": 684, + "sources": 1, + "clones": 1, + "duplicatedLines": 5, + "duplicatedTokens": 38, + "percentage": 3.85, + "percentageTokens": 5.56, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/system-maintenance/bin/periodic-system-maintenance.sh": { + "lines": 51, + "tokens": 270, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/system-maintenance/bin/hosts-file-monitor.sh": { + "lines": 109, + "tokens": 602, + "sources": 1, + "clones": 1, + "duplicatedLines": 5, + "duplicatedTokens": 38, + "percentage": 4.59, + "percentageTokens": 6.31, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/system-maintenance/bin/browser-preexec-wrapper.sh": { + "lines": 32, + "tokens": 206, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/upgrade.sh": { + "lines": 3, + "tokens": 50, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/transcribe.sh": { + "lines": 490, + "tokens": 677, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/process_table.sh": { + "lines": 51, + "tokens": 305, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/libre_translate.sh": { + "lines": 487, + "tokens": 2995, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/install_unity_mcp.sh": { + "lines": 232, + "tokens": 1385, + "sources": 1, + "clones": 1, + "duplicatedLines": 6, + "duplicatedTokens": 49, + "percentage": 2.59, + "percentageTokens": 3.54, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/install_ffmpeg_with_arnndn.sh": { + "lines": 124, + "tokens": 457, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh": { + "lines": 190, + "tokens": 521, + "sources": 1, + "clones": 2, + "duplicatedLines": 50, + "duplicatedTokens": 120, + "percentage": 26.32, + "percentageTokens": 23.03, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/generate_subfolders.sh": { + "lines": 81, + "tokens": 532, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/fix_unity.sh": { + "lines": 301, + "tokens": 751, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/fix_thorium_unity.sh": { + "lines": 159, + "tokens": 329, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/download.sh": { + "lines": 45, + "tokens": 324, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/copyFolder.sh": { + "lines": 27, + "tokens": 173, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/convert.sh": { + "lines": 85, + "tokens": 532, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/compress_images.sh": { + "lines": 28, + "tokens": 145, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/clean_audio.sh": { + "lines": 418, + "tokens": 2424, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pacman/pacman_wrapper.sh": { + "lines": 667, + "tokens": 3868, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh": { + "lines": 84, + "tokens": 570, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/psychological/unlock-hosts.sh": { + "lines": 69, + "tokens": 549, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh": { + "lines": 28, + "tokens": 60, + "sources": 1, + "clones": 1, + "duplicatedLines": 9, + "duplicatedTokens": 34, + "percentage": 32.14, + "percentageTokens": 56.67, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh": { + "lines": 31, + "tokens": 62, + "sources": 1, + "clones": 1, + "duplicatedLines": 9, + "duplicatedTokens": 34, + "percentage": 29.03, + "percentageTokens": 54.84, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/pacman-hooks/hosts-guard-common.sh": { + "lines": 90, + "tokens": 750, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/update_android_hosts.sh": { + "lines": 189, + "tokens": 418, + "sources": 1, + "clones": 1, + "duplicatedLines": 17, + "duplicatedTokens": 37, + "percentage": 8.99, + "percentageTokens": 8.85, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/txt_to_image.sh": { + "lines": 169, + "tokens": 509, + "sources": 1, + "clones": 1, + "duplicatedLines": 9, + "duplicatedTokens": 47, + "percentage": 5.33, + "percentageTokens": 9.23, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/turn_off_auto_idle_screen_shutdown.sh": { + "lines": 270, + "tokens": 1650, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/toggle_window_manager.sh": { + "lines": 105, + "tokens": 601, + "sources": 1, + "clones": 1, + "duplicatedLines": 6, + "duplicatedTokens": 49, + "percentage": 5.71, + "percentageTokens": 8.15, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/toggle_wheel.sh": { + "lines": 56, + "tokens": 376, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/toggle_mic.sh": { + "lines": 47, + "tokens": 278, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/to_webm.sh": { + "lines": 3, + "tokens": 20, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/to_mp4.sh": { + "lines": 3, + "tokens": 20, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/steam_compatibility.sh": { + "lines": 662, + "tokens": 5317, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/sort_downloads.sh": { + "lines": 34, + "tokens": 203, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/setup_passwordless_system.sh": { + "lines": 373, + "tokens": 1662, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/setup_media_organizer.sh": { + "lines": 71, + "tokens": 245, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/setup_android_adblock.sh": { + "lines": 173, + "tokens": 670, + "sources": 1, + "clones": 1, + "duplicatedLines": 17, + "duplicatedTokens": 37, + "percentage": 9.83, + "percentageTokens": 5.52, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/pdf_to_image.sh": { + "lines": 116, + "tokens": 576, + "sources": 1, + "clones": 1, + "duplicatedLines": 23, + "duplicatedTokens": 47, + "percentage": 19.83, + "percentageTokens": 8.16, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/organize_downloads.sh": { + "lines": 407, + "tokens": 2847, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/image_to_resolution.sh": { + "lines": 89, + "tokens": 271, + "sources": 1, + "clones": 1, + "duplicatedLines": 9, + "duplicatedTokens": 47, + "percentage": 10.11, + "percentageTokens": 17.34, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/format_sd_card.sh": { + "lines": 322, + "tokens": 1962, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/convert_words.sh": { + "lines": 28, + "tokens": 140, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/utils/convert_video.sh": { + "lines": 237, + "tokens": 1546, + "sources": 1, + "clones": 1, + "duplicatedLines": 23, + "duplicatedTokens": 47, + "percentage": 9.7, + "percentageTokens": 3.04, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/meta/shell_check.sh": { + "lines": 427, + "tokens": 2943, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/lib/common.sh": { + "lines": 396, + "tokens": 2080, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/lib/android.sh": { + "lines": 49, + "tokens": 268, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/fixes/nvidia_troubleshoot.sh": { + "lines": 332, + "tokens": 1554, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/fixes/fix_virtualbox.sh": { + "lines": 182, + "tokens": 550, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/fixes/fix_systemctl.sh": { + "lines": 83, + "tokens": 299, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/fixes/fix_controller.sh": { + "lines": 195, + "tokens": 1327, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/features/setup_activitywatch.sh": { + "lines": 456, + "tokens": 1399, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/features/raspberry_pi_flash_sd.sh": { + "lines": 660, + "tokens": 3149, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/features/install_unreal_mcp_kvick.sh": { + "lines": 242, + "tokens": 1488, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/features/install_unreal_mcp.sh": { + "lines": 394, + "tokens": 1181, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/features/control_from_mobile.sh": { + "lines": 414, + "tokens": 2172, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/youtube-music-wrapper.sh": { + "lines": 21, + "tokens": 85, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/setup_pc_startup_monitor.sh": { + "lines": 556, + "tokens": 1874, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/setup_midnight_shutdown.sh": { + "lines": 723, + "tokens": 2372, + "sources": 1, + "clones": 2, + "duplicatedLines": 12, + "duplicatedTokens": 82, + "percentage": 1.66, + "percentageTokens": 3.46, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/remove_guest_mode.sh": { + "lines": 162, + "tokens": 908, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pc_startup_visual_status.sh": { + "lines": 285, + "tokens": 2252, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/music_parallelism.sh": { + "lines": 347, + "tokens": 988, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/install_leechblock.sh": { + "lines": 377, + "tokens": 2360, + "sources": 1, + "clones": 1, + "duplicatedLines": 15, + "duplicatedTokens": 59, + "percentage": 3.98, + "percentageTokens": 2.5, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/block_compulsive_opening.sh": { + "lines": 422, + "tokens": 1563, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/wifi_monitor.sh": { + "lines": 26, + "tokens": 173, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/warp_status.sh": { + "lines": 25, + "tokens": 148, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/volume.sh": { + "lines": 18, + "tokens": 101, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/pc_startup_status.sh": { + "lines": 71, + "tokens": 433, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/network_monitor.sh": { + "lines": 87, + "tokens": 550, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/motherboard_temp.sh": { + "lines": 25, + "tokens": 164, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/gpu_monitor.sh": { + "lines": 63, + "tokens": 460, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/cpu_monitor.sh": { + "lines": 47, + "tokens": 337, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/bluetooth.sh": { + "lines": 13, + "tokens": 83, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/battery_status.sh": { + "lines": 10, + "tokens": 20, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/i3blocks/activitywatch_status.sh": { + "lines": 47, + "tokens": 228, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/setup_hosts_guard.sh": { + "lines": 405, + "tokens": 1449, + "sources": 1, + "clones": 1, + "duplicatedLines": 15, + "duplicatedTokens": 59, + "percentage": 3.7, + "percentageTokens": 4.07, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/install_pacman_hooks.sh": { + "lines": 48, + "tokens": 109, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/enforce-hosts.sh": { + "lines": 31, + "tokens": 193, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/test_removal.sh": { + "lines": 40, + "tokens": 240, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/test_bad.sh": { + "lines": 4, + "tokens": 23, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/setup_thorium_startup.sh": { + "lines": 448, + "tokens": 1071, + "sources": 1, + "clones": 1, + "duplicatedLines": 14, + "duplicatedTokens": 30, + "percentage": 3.13, + "percentageTokens": 2.8, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/setup_periodic_system.sh": { + "lines": 324, + "tokens": 1265, + "sources": 1, + "clones": 1, + "duplicatedLines": 14, + "duplicatedTokens": 30, + "percentage": 4.32, + "percentageTokens": 2.37, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/check_and_enable_services.sh": { + "lines": 608, + "tokens": 1406, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "i3-configuration/install.sh": { + "lines": 48, + "tokens": 344, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/install.sh": { + "lines": 425, + "tokens": 1765, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/main.sh": { + "lines": 307, + "tokens": 2262, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/install_nvidia_driver.sh": { + "lines": 106, + "tokens": 1264, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/install_intel_driver.sh": { + "lines": 107, + "tokens": 769, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/install_amd_driver.sh": { + "lines": 153, + "tokens": 1344, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/detect_gpu_and_install.sh": { + "lines": 4, + "tokens": 28, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/detect_gpu.sh": { + "lines": 51, + "tokens": 214, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 17630, + "tokens": 85787, + "sources": 91, + "clones": 10, + "duplicatedLines": 129, + "duplicatedTokens": 442, + "percentage": 0.73, + "percentageTokens": 0.52, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "markdown": { + "sources": { + "scripts/misc/testsAndMisc-bash/mcp_readme.md": { + "lines": 186, + "tokens": 1416, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/misc/testsAndMisc-bash/README_clean_audio.md": { + "lines": 101, + "tokens": 1095, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pacman/pacman_whitelist.txt": { + "lines": 209, + "tokens": 280, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pacman/pacman_greylist.txt": { + "lines": 0, + "tokens": 0, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "scripts/digital_wellbeing/pacman/pacman_blocked_keywords.txt": { + "lines": 52, + "tokens": 104, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "hosts/guard/README.md": { + "lines": 27, + "tokens": 397, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/pacman_packages.txt": { + "lines": 300, + "tokens": 548, + "sources": 1, + "clones": 2, + "duplicatedLines": 88, + "duplicatedTokens": 176, + "percentage": 29.33, + "percentageTokens": 32.12, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/packages.txt": { + "lines": 264, + "tokens": 528, + "sources": 1, + "clones": 2, + "duplicatedLines": 88, + "duplicatedTokens": 176, + "percentage": 33.33, + "percentageTokens": 33.33, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/aur_packages.txt": { + "lines": 98, + "tokens": 394, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "fresh-install/README.md": { + "lines": 55, + "tokens": 396, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + }, + ".github/copilot-instructions.md": { + "lines": 40, + "tokens": 1150, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 1332, + "tokens": 6308, + "sources": 11, + "clones": 2, + "duplicatedLines": 88, + "duplicatedTokens": 176, + "percentage": 6.61, + "percentageTokens": 2.79, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "yaml": { + "sources": { + ".github/workflows/shell-check.yml": { + "lines": 49, + "tokens": 247, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 49, + "tokens": 247, + "sources": 1, + "clones": 0, + "duplicatedLines": 0, + "duplicatedTokens": 0, + "percentage": 0, + "percentageTokens": 0, + "newDuplicatedLines": 0, + "newClones": 0 + } + } + }, + "total": { + "lines": 19426, + "tokens": 96563, + "sources": 105, + "clones": 12, + "duplicatedLines": 217, + "duplicatedTokens": 618, + "percentage": 1.12, + "percentageTokens": 0.64, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "duplicates": [ + { + "format": "bash", + "lines": 6, + "fragment": "# Function to log with timestamp\nlog_message() {\n echo \"$(date '+%Y-%m-%d %H:%M:%S') - $1\" | tee -a \"$LOG_FILE\" >&2\n}\n\n# Function to check if hosts file needs restoration", + "tokens": 0, + "firstFile": { + "name": "scripts/system-maintenance/bin/hosts-file-monitor.sh", + "start": 12, + "end": 17, + "startLoc": { + "line": 12, + "column": 1, + "position": 29 + }, + "endLoc": { + "line": 17, + "column": 52, + "position": 67 + } + }, + "secondFile": { + "name": "scripts/system-maintenance/bin/shutdown-timer-monitor.sh", + "start": 13, + "end": 18, + "startLoc": { + "line": 13, + "column": 1, + "position": 33 + }, + "endLoc": { + "line": 18, + "column": 52, + "position": 71 + } + } + }, + { + "format": "bash", + "lines": 26, + "fragment": "\"; do\n echo \"Attempting to download RNNoise model from: $u\" >&2\n tmp=$(mktemp)\n if has_cmd curl; then\n if curl -fsSL \"$u\" -o \"$tmp\"; then\n if [[ -s $tmp ]]; then\n mv \"$tmp\" \"$dest\"\n echo \"Saved RNNoise model to: $dest\" >&2\n exit 0\n fi\n fi\n else\n if wget -qO \"$tmp\" \"$u\"; then\n if [[ -s $tmp ]]; then\n mv \"$tmp\" \"$dest\"\n echo \"Saved RNNoise model to: $dest\" >&2\n exit 0\n fi\n fi\n fi\n rm -f \"$tmp\" || true\ndone\n\n# Priority 3: repo archives (rnnoise-nu and arnndn-models)\nARCHIVES=(\n \"", + "tokens": 0, + "firstFile": { + "name": "scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh", + "start": 109, + "end": 134, + "startLoc": { + "line": 109, + "column": 17, + "position": 255 + }, + "endLoc": { + "line": 134, + "column": 4, + "position": 315 + } + }, + "secondFile": { + "name": "scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh", + "start": 82, + "end": 107, + "startLoc": { + "line": 82, + "column": 14, + "position": 191 + }, + "endLoc": { + "line": 107, + "column": 4, + "position": 251 + } + } + }, + { + "format": "bash", + "lines": 10, + "fragment": "set -euo pipefail\n\n# Source shared functions\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n# shellcheck source=hosts-guard-common.sh\nsource \"$SCRIPT_DIR/hosts-guard-common.sh\"\n\nENFORCE=/usr/local/sbin/enforce-hosts.sh\n\nlog_hook \"", + "tokens": 0, + "firstFile": { + "name": "hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh", + "start": 4, + "end": 13, + "startLoc": { + "line": 4, + "column": 1, + "position": 5 + }, + "endLoc": { + "line": 13, + "column": 11, + "position": 39 + } + }, + "secondFile": { + "name": "hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh", + "start": 4, + "end": 17, + "startLoc": { + "line": 4, + "column": 1, + "position": 5 + }, + "endLoc": { + "line": 17, + "column": 11, + "position": 39 + } + } + }, + { + "format": "bash", + "lines": 7, + "fragment": "\"; do\n if ! pacman -Qi \"$pkg\" > /dev/null 2>&1; then\n missing+=(\"$pkg\")\n fi\n done\n\n if [", + "tokens": 0, + "firstFile": { + "name": "scripts/utils/toggle_window_manager.sh", + "start": 42, + "end": 48, + "startLoc": { + "line": 42, + "column": 22, + "position": 264 + }, + "endLoc": { + "line": 48, + "column": 2, + "position": 313 + } + }, + "secondFile": { + "name": "scripts/misc/testsAndMisc-bash/install_unity_mcp.sh", + "start": 41, + "end": 47, + "startLoc": { + "line": 41, + "column": 15, + "position": 277 + }, + "endLoc": { + "line": 47, + "column": 3, + "position": 326 + } + } + }, + { + "format": "bash", + "lines": 18, + "fragment": "#!/bin/bash\n\nset -euo pipefail\n\n# Source common library\nSCRIPT_DIR=\"$(dirname \"$(readlink -f \"$0\")\")\"\n# shellcheck source=../lib/common.sh\nsource \"$SCRIPT_DIR/../lib/common.sh\"\n# shellcheck source=../lib/android.sh\nsource \"$SCRIPT_DIR/../lib/android.sh\"\n\n# Re-run with sudo if needed for reading /etc/hosts\nrequire_hosts_readable \"$@\"\n\nWORK_DIR=\"$ANDROID_WORK_DIR\"\n\ninstall_adaway() {\n\tprint_header \"", + "tokens": 0, + "firstFile": { + "name": "scripts/utils/setup_android_adblock.sh", + "start": 1, + "end": 18, + "startLoc": { + "line": 1, + "column": 1, + "position": 0 + }, + "endLoc": { + "line": 18, + "column": 16, + "position": 37 + } + }, + "secondFile": { + "name": "scripts/utils/update_android_hosts.sh", + "start": 1, + "end": 17, + "startLoc": { + "line": 1, + "column": 1, + "position": 0 + }, + "endLoc": { + "line": 17, + "column": 6, + "position": 37 + } + } + }, + { + "format": "bash", + "lines": 10, + "fragment": "is not installed.\"\n echo \"Install it with:\"\n echo \" Arch Linux: sudo pacman -S imagemagick\"\n echo \" Ubuntu/Debian: sudo apt install imagemagick\"\n exit 1\nfi\n\n# Parse arguments\nif [[ $# -lt 1 ]]; then\n echo \"Error: Missing required argument /dev/null; then + _status="ok" + fi + else + err "Setup script not found: $setup_script" + fi + fi + fi + + SERVICE_STATUS["$status_key"]=$_status +} + ###################################################################### # Check functions ###################################################################### @@ -134,7 +176,7 @@ check_pacman_wrapper() { if [[ -L /usr/bin/pacman ]]; then local target target=$(readlink -f /usr/bin/pacman) - if [[ "$target" == "/usr/local/bin/pacman_wrapper" ]]; then + if [[ $target == "/usr/local/bin/pacman_wrapper" ]]; then msg "Pacman symlink points to wrapper" else issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)") @@ -171,7 +213,7 @@ check_pacman_wrapper() { done # Report and fix - if [[ "$status" == "error" ]]; then + if [[ $status == "error" ]]; then for issue in "${issues[@]}"; do err "$issue" done @@ -179,7 +221,7 @@ check_pacman_wrapper() { if [[ $STATUS_ONLY -eq 0 ]]; then note "Installing pacman wrapper..." - if [[ -f "$PACMAN_WRAPPER_INSTALL" ]]; then + if [[ -f $PACMAN_WRAPPER_INSTALL ]]; then run bash "$PACMAN_WRAPPER_INSTALL" ((FIXES_APPLIED++)) || true # Re-verify after fix @@ -232,33 +274,11 @@ check_midnight_shutdown() { status="error" fi - # Report and fix - if [[ "$status" != "ok" ]]; then - for issue in "${issues[@]}"; do - if [[ "$status" == "error" ]]; then - err "$issue" - else - warn "$issue" - fi - done - ((ISSUES_FOUND++)) || true - - if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then - note "Setting up midnight shutdown..." - if [[ -f "$MIDNIGHT_SHUTDOWN_SCRIPT" ]]; then - run bash "$MIDNIGHT_SHUTDOWN_SCRIPT" enable - ((FIXES_APPLIED++)) || true - # Re-verify after fix - if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then - status="ok" - fi - else - err "Setup script not found: $MIDNIGHT_SHUTDOWN_SCRIPT" - fi - fi - fi - - SERVICE_STATUS["midnight_shutdown"]=$status + report_and_fix issues status "midnight_shutdown" \ + "Setting up midnight shutdown..." \ + "$MIDNIGHT_SHUTDOWN_SCRIPT" \ + "day-specific-shutdown.timer" \ + enable } check_startup_monitor() { @@ -298,33 +318,10 @@ check_startup_monitor() { status="error" fi - # Report and fix - if [[ "$status" != "ok" ]]; then - for issue in "${issues[@]}"; do - if [[ "$status" == "error" ]]; then - err "$issue" - else - warn "$issue" - fi - done - ((ISSUES_FOUND++)) || true - - if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then - note "Setting up startup monitor..." - if [[ -f "$STARTUP_MONITOR_SCRIPT" ]]; then - run bash "$STARTUP_MONITOR_SCRIPT" - ((FIXES_APPLIED++)) || true - # Re-verify after fix - if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then - status="ok" - fi - else - err "Setup script not found: $STARTUP_MONITOR_SCRIPT" - fi - fi - fi - - SERVICE_STATUS["startup_monitor"]=$status + report_and_fix issues status "startup_monitor" \ + "Setting up startup monitor..." \ + "$STARTUP_MONITOR_SCRIPT" \ + "pc-startup-monitor.timer" } check_periodic_systems() { @@ -379,33 +376,10 @@ check_periodic_systems() { status="error" fi - # Report and fix - if [[ "$status" != "ok" ]]; then - for issue in "${issues[@]}"; do - if [[ "$status" == "error" ]]; then - err "$issue" - else - warn "$issue" - fi - done - ((ISSUES_FOUND++)) || true - - if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then - note "Setting up periodic systems..." - if [[ -f "$PERIODIC_SYSTEM_SCRIPT" ]]; then - run bash "$PERIODIC_SYSTEM_SCRIPT" - ((FIXES_APPLIED++)) || true - # Re-verify after fix - if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled periodic-system-maintenance.timer &>/dev/null; then - status="ok" - fi - else - err "Setup script not found: $PERIODIC_SYSTEM_SCRIPT" - fi - fi - fi - - SERVICE_STATUS["periodic_systems"]=$status + report_and_fix issues status "periodic_systems" \ + "Setting up periodic systems..." \ + "$PERIODIC_SYSTEM_SCRIPT" \ + "periodic-system-maintenance.timer" } check_hosts() { @@ -432,7 +406,7 @@ check_hosts() { # Check if hosts file is immutable local attrs attrs=$(lsattr /etc/hosts 2>/dev/null | cut -d' ' -f1 || echo "") - if [[ "$attrs" == *"i"* ]]; then + if [[ $attrs == *"i"* ]]; then msg "/etc/hosts has immutable attribute set" else issues+=("/etc/hosts is not immutable") @@ -503,9 +477,9 @@ check_hosts() { fi # Report issues - if [[ "$status" != "ok" ]]; then + if [[ $status != "ok" ]]; then for issue in "${issues[@]}"; do - if [[ "$status" == "error" ]]; then + if [[ $status == "error" ]]; then err "$issue" else warn "$issue" @@ -517,7 +491,7 @@ check_hosts() { # Run hosts install first if [[ ! -f /etc/hosts ]] || [[ $(wc -l /dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then note "Setting up hosts guard..." - if [[ -f "$HOSTS_GUARD_SCRIPT" ]]; then + if [[ -f $HOSTS_GUARD_SCRIPT ]]; then run bash "$HOSTS_GUARD_SCRIPT" ((FIXES_APPLIED++)) || true else @@ -539,7 +513,7 @@ check_hosts() { # Install pacman hooks if missing if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then note "Installing pacman hooks..." - if [[ -f "$HOSTS_PACMAN_HOOKS_SCRIPT" ]]; then + if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then run bash "$HOSTS_PACMAN_HOOKS_SCRIPT" ((FIXES_APPLIED++)) || true else diff --git a/scripts/digital_wellbeing/block_compulsive_opening.sh b/scripts/digital_wellbeing/block_compulsive_opening.sh index d934c49..302d3dc 100755 --- a/scripts/digital_wellbeing/block_compulsive_opening.sh +++ b/scripts/digital_wellbeing/block_compulsive_opening.sh @@ -66,10 +66,10 @@ was_opened_this_hour() { local current_hour current_hour=$(get_hour_key) - if [[ -f "$state_file" ]]; then + if [[ -f $state_file ]]; then local last_hour last_hour=$(cat "$state_file" 2>/dev/null || echo "") - if [[ "$last_hour" == "$current_hour" ]]; then + if [[ $last_hour == "$current_hour" ]]; then return 0 # Was opened this hour fi fi @@ -152,13 +152,13 @@ install_wrapper() { fi # Check if wrapper location exists (file or symlink) - if [[ ! -e "$wrapper_path" && ! -L "$wrapper_path" ]]; then + if [[ ! -e $wrapper_path && ! -L $wrapper_path ]]; then echo " ⚠ $app not installed ($wrapper_path not found)" return 1 fi # Check if real binary exists - if [[ ! -x "$real_binary" ]]; then + if [[ ! -x $real_binary ]]; then echo " ⚠ $app real binary not found ($real_binary)" return 1 fi @@ -166,7 +166,7 @@ install_wrapper() { echo " Installing wrapper for $app..." # Handle symlinks: save the symlink itself, not the target - if [[ -L "$wrapper_path" ]]; then + if [[ -L $wrapper_path ]]; then local link_target link_target=$(readlink "$wrapper_path") echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig" @@ -207,7 +207,7 @@ uninstall_wrapper() { # Check if it was a symlink (stored as SYMLINK:target in .orig) local orig_content orig_content=$(cat "${wrapper_path}.orig" 2>/dev/null || echo "") - if [[ "$orig_content" == SYMLINK:* ]]; then + if [[ $orig_content == SYMLINK:* ]]; then local link_target="${orig_content#SYMLINK:}" echo " Restoring symlink $wrapper_path -> $link_target" ln -s "$link_target" "$wrapper_path" @@ -229,7 +229,7 @@ install_all() { script_path="$(readlink -f "$0")" local install_path="/usr/local/bin/block-compulsive-opening.sh" - if [[ "$script_path" != "$install_path" ]]; then + if [[ $script_path != "$install_path" ]]; then echo "Installing main script to $install_path..." cp "$script_path" "$install_path" chmod +x "$install_path" @@ -287,10 +287,10 @@ show_status() { local status="not opened this hour" local icon="○" - if [[ -f "$state_file" ]]; then + if [[ -f $state_file ]]; then local last_hour last_hour=$(cat "$state_file" 2>/dev/null || echo "") - if [[ "$last_hour" == "$current_hour" ]]; then + if [[ $last_hour == "$current_hour" ]]; then status="already opened (blocked until next hour)" icon="●" else @@ -303,7 +303,7 @@ show_status() { local wrapper_path="${APPS[$app]}" if [[ -f "${wrapper_path}.orig" ]]; then wrapped="wrapped" - elif [[ -f "$wrapper_path" ]]; then + elif [[ -f $wrapper_path ]]; then wrapped="installed (not wrapped)" fi @@ -320,7 +320,7 @@ reset_app() { local state_file state_file=$(get_state_file "$app") - if [[ -f "$state_file" ]]; then + if [[ -f $state_file ]]; then rm -f "$state_file" echo "Reset $app - can be opened again this hour" log_message "RESET: $app state cleared by user" @@ -392,7 +392,7 @@ main() { show_status ;; reset) - if [[ -z "${2:-}" ]]; then + if [[ -z ${2:-} ]]; then echo "Error: specify app to reset" echo "Apps: ${!APPS[*]}" exit 1 @@ -403,7 +403,7 @@ main() { reset_all ;; wrapper) - if [[ -z "${2:-}" ]]; then + if [[ -z ${2:-} ]]; then echo "Error: wrapper requires app name" exit 1 fi diff --git a/scripts/digital_wellbeing/music_parallelism.sh b/scripts/digital_wellbeing/music_parallelism.sh index aab9efd..8abc751 100755 --- a/scripts/digital_wellbeing/music_parallelism.sh +++ b/scripts/digital_wellbeing/music_parallelism.sh @@ -107,12 +107,12 @@ kill_music_services() { local yt_music_windows yt_music_windows=$(xdotool search --name "YouTube Music" 2>/dev/null || true) for wid in $yt_music_windows; do - if [[ -n "$wid" ]]; then + if [[ -n $wid ]]; then # Get window name for logging local wname wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown") # Only close if it's YouTube Music, not regular YouTube - if [[ "$wname" == *"YouTube Music"* ]] || [[ "$wname" == *"music.youtube.com"* ]]; then + if [[ $wname == *"YouTube Music"* ]] || [[ $wname == *"music.youtube.com"* ]]; then log_message "Closing YouTube Music window: $wname (ID: $wid)" xdotool windowclose "$wid" 2>/dev/null || true killed=true @@ -152,7 +152,7 @@ kill_music_services() { local windows windows=$(xdotool search --name "$pattern" 2>/dev/null || true) for wid in $windows; do - if [[ -n "$wid" ]]; then + if [[ -n $wid ]]; then local wname wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown") log_message "Closing music service window: $wname (ID: $wid)" diff --git a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh index b97edcf..e7d6a47 100755 --- a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh +++ b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh @@ -220,8 +220,6 @@ function is_greylisted_package_name() { return 1 } -# Helper: detect if current invocation includes --noconfirm - # Helper: detect if current invocation includes --noconfirm function has_noconfirm_flag() { for arg in "$@"; do @@ -232,6 +230,27 @@ function has_noconfirm_flag() { return 1 } +# Helper: get list of PIDs holding a lock file (excluding our own PID) +# Populates the $holders array +get_lock_holders() { + local lock_file="$1" + holders=() + if command -v fuser >/dev/null 2>&1; then + mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) + elif command -v lsof >/dev/null 2>&1; then + mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true) + fi + # Filter out our own PID + if [[ ${#holders[@]} -gt 0 ]]; then + local -a filtered=() + for pid in "${holders[@]}"; do + [[ $pid -eq $$ ]] && continue + filtered+=("$pid") + done + holders=("${filtered[@]}") + fi +} + # Handle stale pacman database lock if present and no package managers are running check_and_handle_db_lock() { local lock_file="/var/lib/pacman/db.lck" @@ -242,23 +261,7 @@ check_and_handle_db_lock() { # Determine which processes actually have the lock open local -a holders=() - if command -v fuser >/dev/null 2>&1; then - mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) - elif command -v lsof >/dev/null 2>&1; then - mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true) - else - holders=() - fi - - # Filter out our own PID if it somehow appears - if [[ ${#holders[@]} -gt 0 ]]; then - local -a filtered=() - for pid in "${holders[@]}"; do - [[ $pid -eq $$ ]] && continue - filtered+=("$pid") - done - holders=("${filtered[@]}") - fi + get_lock_holders "$lock_file" if [[ ${#holders[@]} -gt 0 ]]; then local pac_holder=0 @@ -292,12 +295,7 @@ check_and_handle_db_lock() { sleep 1 # Re-check holders - holders=() - if command -v fuser >/dev/null 2>&1; then - mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true) - elif command -v lsof >/dev/null 2>&1; then - mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true) - fi + get_lock_holders "$lock_file" if [[ ${#holders[@]} -gt 0 ]]; then echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2 return 1 @@ -305,6 +303,16 @@ check_and_handle_db_lock() { fi fi + # Helper to remove a file with sudo if needed + remove_file_as_root() { + local f="$1" + if [[ $EUID -ne 0 ]]; then + sudo rm -f "$f" + else + rm -f "$f" + fi + } + # Decide whether to remove the lock local now epoch age if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then @@ -317,11 +325,7 @@ check_and_handle_db_lock() { # Auto-remove in non-interactive mode (--noconfirm) or if the lock is older than 10 minutes if has_noconfirm_flag "$@" || [[ $age -ge 600 ]]; then echo -e "${YELLOW}Stale pacman lock detected (age: ${age}s). Removing it automatically...${NC}" >&2 - if [[ $EUID -ne 0 ]]; then - sudo rm -f "$lock_file" || return 1 - else - rm -f "$lock_file" || return 1 - fi + remove_file_as_root "$lock_file" || return 1 return 0 fi @@ -330,25 +334,23 @@ check_and_handle_db_lock() { echo -e "${CYAN}Lock path:${NC} $lock_file (age: ${age}s)" >&2 read -r -t 15 -p $'Remove stale lock and continue? [y/N]: ' reply || reply="n" if [[ ${reply,,} == "y" || ${reply,,} == "yes" ]]; then - if [[ $EUID -ne 0 ]]; then - sudo rm -f "$lock_file" || return 1 - else - rm -f "$lock_file" || return 1 - fi + remove_file_as_root "$lock_file" || return 1 return 0 fi echo -e "${RED}Aborting due to existing pacman lock. Close other updaters and retry, or run with --noconfirm to auto-clear stale locks.${NC}" >&2 return 1 } -# Cleanup: remove any installed blocked packages (in addition to the queued operation) -function remove_installed_blocked_packages() { - # args not used; kept for future policy extension - # List installed package names +# Generic function to remove installed packages matching a filter +# Args: check_function label_prefix +function remove_installed_packages_matching() { + local check_function="$1" + local label="$2" + mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null) local to_remove=() for name in "${installed_names[@]}"; do - if is_blocked_package_name "$name"; then + if "$check_function" "$name"; then to_remove+=("$name") fi done @@ -357,83 +359,59 @@ function remove_installed_blocked_packages() { return 0 fi - echo -e "${YELLOW}Policy cleanup:${NC} Removing blocked installed packages: ${BOLD}${to_remove[*]}${NC}" >&2 - local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm) - "${remove_cmd[@]}" "${to_remove[@]}" + echo -e "${YELLOW}${label} cleanup:${NC} Removing packages: ${BOLD}${to_remove[*]}${NC}" >&2 + "$PACMAN_BIN" -Rns --noconfirm "${to_remove[@]}" local rc=$? if [[ $rc -ne 0 ]]; then - echo -e "${RED}Cleanup removal failed with exit code ${rc}.${NC}" >&2 + echo -e "${RED}${label} cleanup removal failed with exit code ${rc}.${NC}" >&2 else - echo -e "${GREEN}Cleanup removal completed for: ${to_remove[*]}${NC}" >&2 + echo -e "${GREEN}${label} cleanup removal completed for: ${to_remove[*]}${NC}" >&2 fi return $rc } -# Cleanup: remove any installed greylisted packages (challenge-required packages) +# Cleanup: remove any installed blocked packages +function remove_installed_blocked_packages() { + remove_installed_packages_matching is_blocked_package_name "Policy" +} + +# Cleanup: remove any installed greylisted packages function remove_installed_greylisted_packages() { - # List installed package names - mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null) - local to_remove=() - for name in "${installed_names[@]}"; do - if is_greylisted_package_name "$name"; then - to_remove+=("$name") - fi - done + remove_installed_packages_matching is_greylisted_package_name "Greylist" +} - if [[ ${#to_remove[@]} -eq 0 ]]; then - return 0 +# Helper: Check if this is an install command and run a filter on each package name +# Usage: check_install_for filter_func "$@" +# Returns 0 if filter_func matches any package +function check_install_for() { + local filter_func="$1" + shift + # Check if the command is an installation command + if [[ ${1:-} == "-S" || ${1:-} == "-Sy" || ${1:-} == "-Syu" || ${1:-} == "-Syyu" || ${1:-} == "-U" ]]; then + for arg in "$@"; do + # Strip repository prefix if present (like extra/ or community/) + local package_name="${arg##*/}" + if "$filter_func" "$package_name"; then + return 0 + fi + done fi - - echo -e "${YELLOW}Greylist cleanup:${NC} Removing greylisted installed packages: ${BOLD}${to_remove[*]}${NC}" >&2 - local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm) - "${remove_cmd[@]}" "${to_remove[@]}" - local rc=$? - if [[ $rc -ne 0 ]]; then - echo -e "${RED}Greylist cleanup removal failed with exit code ${rc}.${NC}" >&2 - else - echo -e "${GREEN}Greylist cleanup removal completed for: ${to_remove[*]}${NC}" >&2 - fi - return $rc + return 1 } # Function to check if user is trying to install packages that are always blocked function check_for_always_blocked() { - # Check if the command is an installation command - if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present (like extra/ or community/) - local package_name="${arg##*/}" - if is_blocked_package_name "$package_name"; then - return 0 # Always blocked package found - fi - done - fi - return 1 # No always blocked package found + check_install_for is_blocked_package_name "$@" +} + +# Helper to check if a package name is steam +function is_steam_package() { + [[ $1 == "steam" ]] } # Function to check if user is trying to install steam (challenge-eligible package) function check_for_steam() { - # List of packages that require challenge (only steam in this case) - local steam_packages=("steam") - - # Check if the command is an installation command - if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present (like extra/ or community/) - local package_name="${arg##*/}" - - # Check if argument matches steam - for package in "${steam_packages[@]}"; do - if [[ $arg == "$package" || $arg == *"/$package-"* || $arg == *"/$package/"* || - $arg == *"/$package" || $package_name == "$package" ]]; then - return 0 # Steam package found - fi - done - done - fi - return 1 # No steam package found + check_install_for is_steam_package "$@" } # Function to check if current day is a weekday (after 4PM Friday until midnight Sunday) @@ -459,6 +437,121 @@ function is_weekday() { fi } +# Unified word unscrambling challenge function +# Args: challenge_name word_length words_count timeout_seconds initial_delay_max post_delay_min post_delay_range +function run_word_challenge() { + local challenge_name="$1" + local word_length="$2" + local words_count="$3" + local timeout_seconds="$4" + local initial_delay_max="${5:-20}" + local post_delay_min="${6:-0}" + local post_delay_range="${7:-20}" + + echo -e "${YELLOW}${challenge_name} challenge will begin shortly...${NC}" + + # Initial delay + local sleep_duration=$((RANDOM % initial_delay_max)) + sleep "$sleep_duration" + + # Load words file + local script_dir words_file + script_dir="$(dirname "$(readlink -f "$0")")" + words_file="$script_dir/words.txt" + + if [[ ! -f $words_file ]]; then + echo -e "${RED}Error: words.txt file not found at $words_file${NC}" + return 1 + fi + + echo -e "${CYAN}Challenge: Words with ${word_length} letters${NC}" + + # Load random words of specified length + local -a selected_words + mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count") + + if [[ ${#selected_words[@]} -lt $words_count ]]; then + echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}" + words_count=${#selected_words[@]} + if [[ $words_count -eq 0 ]]; then + echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}" + return 1 + fi + fi + + # Convert to uppercase + for i in "${!selected_words[@]}"; do + selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]') + done + + echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" + + # Display words in grid + for ((i = 0; i < words_count; i++)); do + printf "${BLUE}%-15s${NC}" "${selected_words[i]}" + if (((i + 1) % 4 == 0)); then + echo "" + fi + done + + # Select and scramble a word + local target_index target_word scrambled_word + target_index=$((RANDOM % words_count)) + target_word="${selected_words[target_index]}" + scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n') + + if [[ $scrambled_word == "$target_word" ]]; then + scrambled_word=$(echo "$target_word" | rev) + fi + + echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}" + echo -e "${YELLOW}Unscramble the word to proceed (you have $timeout_seconds seconds):${NC}" + + # Timer display background process + ( + local start_time current_time elapsed remaining + start_time=$(date +%s) + while true; do + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + remaining=$((timeout_seconds - elapsed)) + if [[ $remaining -le 0 ]]; then + echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} " + break + fi + echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} " + sleep 1 + done + ) & + local display_pid=$! + + # Read input with timeout + local user_input read_status + read -t "$timeout_seconds" -r user_input + read_status=$? + + kill "$display_pid" 2>/dev/null + wait "$display_pid" 2>/dev/null + echo + + if [[ $read_status -ne 0 ]]; then + echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}" + return 1 + fi + + user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) + + if [[ $user_input == "$target_word" ]]; then + echo -e "${GREEN}Correct! Proceeding with installation...${NC}" + local post_challenge_sleep=$((RANDOM % post_delay_range + post_delay_min)) + [[ $post_challenge_sleep -gt 0 ]] && sleep "$post_challenge_sleep" + return 0 + else + echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}" + return 1 + fi +} + # Function to prompt for solving a word unscrambling challenge (only for steam) function prompt_for_steam_challenge() { echo -e "${YELLOW}WARNING: You are trying to install Steam.${NC}" @@ -472,259 +565,20 @@ function prompt_for_steam_challenge() { return 1 fi - echo -e "${YELLOW}Weekend Steam challenge will begin shortly...${NC}" - - # Sleep for random 20-40 seconds - # sleep_duration=$((RANDOM % 20 + 20)) - sleep_duration=$((RANDOM % 20)) - sleep "$sleep_duration" - - # Define path to words.txt (in the same directory as the script) - script_dir="$(dirname "$(readlink -f "$0")")" - words_file="$script_dir/words.txt" - - # Check if words.txt exists - if [[ ! -f $words_file ]]; then - echo -e "${RED}Error: words.txt file not found at $words_file${NC}" - return 1 - fi - - # Choose a specific word length (5, 6, 7, or 8 characters) - # - word_length=5 - echo -e "${CYAN}Today's challenge: Words with ${word_length} letters${NC}" - - # Filter words by the specific chosen length and load random words - words_count=160 - mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count") - - # If we couldn't get enough words of the right length - if [[ ${#selected_words[@]} -lt $words_count ]]; then - echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}" - words_count=${#selected_words[@]} - if [[ $words_count -eq 0 ]]; then - echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}" - return 1 - fi - fi - - # Convert all words to uppercase - for i in "${!selected_words[@]}"; do - selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]') - done - - echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" - - # Display the words in a grid (4 columns) - for ((i = 0; i < words_count; i++)); do - printf "${BLUE}%-15s${NC}" "${selected_words[i]}" - if (((i + 1) % 4 == 0)); then - echo "" - fi - done - - # Select a random word to scramble (already in uppercase) - target_index=$((RANDOM % words_count)) - target_word="${selected_words[target_index]}" - - # Scramble the word - scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n') - - # Ensure scrambled word is different from original - if [[ $scrambled_word == "$target_word" ]]; then - # Use simple reversal as fallback - scrambled_word=$(echo "$target_word" | rev) - fi - - echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}" - echo -e "${YELLOW}Unscramble the word to proceed with installation (you have 2 minutes):${NC}" - - # Set up a background process to display the timer - ( - start_time=$(date +%s) - while true; do - current_time=$(date +%s) - elapsed=$((current_time - start_time)) - remaining=$((60 - elapsed)) - - if [[ $remaining -le 0 ]]; then - echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} " - break - fi - - echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} " - sleep 1 - done - ) & - display_pid=$! - - # Read user input with timeout - read -t 60 -r user_input - read_status=$? - - # Kill the timer display - kill "$display_pid" 2>/dev/null - wait "$display_pid" 2>/dev/null - echo # Add a newline after the timer - - # Check if read timed out - if [[ $read_status -ne 0 ]]; then - echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}" - return 1 - fi - - # Convert user input to uppercase and trim whitespaces - user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) - - if [[ $user_input == "$target_word" ]]; then - echo -e "${GREEN}Correct! Proceeding with installation...${NC}" - - # Add sleep after successful challenge completion (20-40 seconds) - # post_challenge_sleep=$((RANDOM % 20 + 20)) - post_challenge_sleep=$((RANDOM % 20)) - sleep "$post_challenge_sleep" - - return 0 - else - echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}" - return 1 - fi + # word_length=5, words_count=160, timeout=60s, initial_delay=20, post_delay=0-20 + run_word_challenge "Weekend Steam" 5 160 60 20 0 20 } function check_for_greylisted() { - # Check if the command is an installation command - if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then - # Check all arguments - for arg in "$@"; do - # Strip repository prefix if present - local package_name="${arg##*/}" - - # Check if package name matches any greylisted keyword - if is_greylisted_package_name "$package_name"; then - return 0 # Greylisted package found - fi - done - fi - return 1 # No greylisted package found + check_install_for is_greylisted_package_name "$@" } # Function to prompt for solving a word unscrambling challenge (for greylisted packages - always active) function prompt_for_greylist_challenge() { echo -e "${YELLOW}WARNING: You are trying to install a greylisted package.${NC}" - echo -e "${YELLOW}Greylist challenge will begin shortly...${NC}" - # Sleep for random 10-30 seconds - sleep_duration=$((RANDOM % 20 + 10)) - sleep "$sleep_duration" - - # Define path to words.txt (in the same directory as the script) - script_dir="$(dirname "$(readlink -f "$0")")" - words_file="$script_dir/words.txt" - - # Check if words.txt exists - if [[ ! -f $words_file ]]; then - echo -e "${RED}Error: words.txt file not found at $words_file${NC}" - return 1 - fi - - # Choose a specific word length (6 characters for greylist challenge) - word_length=6 - echo -e "${CYAN}Greylist challenge: Words with ${word_length} letters${NC}" - - # Filter words by the specific chosen length and load random words - words_count=120 - mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count") - - # If we couldn't get enough words of the right length - if [[ ${#selected_words[@]} -lt $words_count ]]; then - echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}" - words_count=${#selected_words[@]} - if [[ $words_count -eq 0 ]]; then - echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}" - return 1 - fi - fi - - # Convert all words to uppercase - for i in "${!selected_words[@]}"; do - selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]') - done - - echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}" - - # Display the words in a grid (4 columns) - for ((i = 0; i < words_count; i++)); do - printf "${BLUE}%-15s${NC}" "${selected_words[i]}" - if (((i + 1) % 4 == 0)); then - echo "" - fi - done - - # Select a random word to scramble (already in uppercase) - target_index=$((RANDOM % words_count)) - target_word="${selected_words[target_index]}" - - # Scramble the word - scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n') - - # Ensure scrambled word is different from original - if [[ $scrambled_word == "$target_word" ]]; then - # Use simple reversal as fallback - scrambled_word=$(echo "$target_word" | rev) - fi - - echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}" - echo -e "${YELLOW}Unscramble the word to proceed with installation (you have 90 seconds):${NC}" - - # Set up a background process to display the timer - ( - start_time=$(date +%s) - while true; do - current_time=$(date +%s) - elapsed=$((current_time - start_time)) - remaining=$((90 - elapsed)) - - if [[ $remaining -le 0 ]]; then - echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} " - break - fi - - echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} " - sleep 1 - done - ) & - display_pid=$! - - # Read user input with timeout (90 seconds for VirtualBox) - read -t 90 -r user_input - read_status=$? - - # Kill the timer display - kill "$display_pid" 2>/dev/null - wait "$display_pid" 2>/dev/null - echo # Add a newline after the timer - - # Check if read timed out - if [[ $read_status -ne 0 ]]; then - echo -e "${RED}Time's up! Greylist challenge failed. The correct word was '$target_word'.${NC}" - return 1 - fi - - # Convert user input to uppercase and trim whitespaces - user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs) - - if [[ $user_input == "$target_word" ]]; then - echo -e "${GREEN}Correct! Proceeding with installation...${NC}" - - # Add sleep after successful challenge completion (15-35 seconds) - post_challenge_sleep=$((RANDOM % 20 + 15)) - sleep "$post_challenge_sleep" - - return 0 - else - echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}" - return 1 - fi + # word_length=6, words_count=120, timeout=90s, initial_delay=30, post_delay=15-35 + run_word_challenge "Greylist" 6 120 90 30 15 20 } # Check for wrapper-specific commands diff --git a/scripts/digital_wellbeing/setup_midnight_shutdown.sh b/scripts/digital_wellbeing/setup_midnight_shutdown.sh index 3a5d3c7..6d3da1d 100755 --- a/scripts/digital_wellbeing/setup_midnight_shutdown.sh +++ b/scripts/digital_wellbeing/setup_midnight_shutdown.sh @@ -6,6 +6,11 @@ set -e # Exit on any error +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Function to show usage show_usage() { echo "Day-Specific Auto-Shutdown Setup for Arch Linux" @@ -35,13 +40,7 @@ check_sudo() { } # Get the actual user (even when running with sudo) -if [[ -n $SUDO_USER ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" -else - ACTUAL_USER="$USER" - USER_HOME="$HOME" -fi +set_actual_user_vars # Function to show current status show_current_status() { @@ -603,6 +602,13 @@ test_setup() { systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available" } +# Display the shutdown schedule (used in multiple places) +print_shutdown_schedule() { + echo "Shutdown Schedule:" + echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" + echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" +} + # Function to show final instructions show_instructions() { echo "" @@ -618,9 +624,7 @@ show_instructions() { echo "✓ Monitor service installed (protects timer from being disabled)" echo "✓ Watchdog timer installed (restarts monitor if stopped)" echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" - echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" + print_shutdown_schedule echo "" echo "Management commands:" echo " sudo day-specific-shutdown-manager.sh status - Check status" @@ -646,9 +650,7 @@ confirm_setup() { echo "===============================================" echo "This will set up your PC to automatically shutdown during specific time windows." echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" - echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" + print_shutdown_schedule echo "" echo "Important considerations:" echo "- Any unsaved work will be lost during shutdown windows" diff --git a/scripts/features/install_unreal_mcp.sh b/scripts/features/install_unreal_mcp.sh index b87bc7b..91ddaeb 100755 --- a/scripts/features/install_unreal_mcp.sh +++ b/scripts/features/install_unreal_mcp.sh @@ -10,14 +10,13 @@ set -euo pipefail SCRIPT_NAME="$(basename "$0")" +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # ---------- User/paths ---------- -if [[ -n ${SUDO_USER:-} ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" -else - ACTUAL_USER="$USER" - USER_HOME="$HOME" -fi +set_actual_user_vars INSTALL_ROOT_DEFAULT="$USER_HOME/.local/share/unreal-mcp" INSTALL_ROOT="$INSTALL_ROOT_DEFAULT" diff --git a/scripts/features/raspberry_pi_flash_sd.sh b/scripts/features/raspberry_pi_flash_sd.sh index ac77096..227ff42 100755 --- a/scripts/features/raspberry_pi_flash_sd.sh +++ b/scripts/features/raspberry_pi_flash_sd.sh @@ -13,7 +13,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf" # Load configuration from gitignored config file if it exists -if [[ -f "$CONFIG_FILE" ]]; then +if [[ -f $CONFIG_FILE ]]; then # shellcheck source=/dev/null source "$CONFIG_FILE" fi @@ -93,7 +93,7 @@ generate_password() { } auto_generate_pi_password() { - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then PI_PASSWORD=$(generate_password 16) log_info "Auto-generated Pi password (will be saved to config file)" fi @@ -150,7 +150,7 @@ discover_remote_laptop() { nmap -sn -T4 "$network" &>/dev/null || true ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) - if [[ -z "$ssh_hosts" ]]; then + if [[ -z $ssh_hosts ]]; then die "No SSH-enabled devices found on network" fi @@ -163,7 +163,7 @@ discover_remote_laptop() { for u in "${common_users[@]}"; do local is_dup=0 for existing in "${users[@]}"; do - if [[ "$u" == "$existing" ]]; then + if [[ $u == "$existing" ]]; then is_dup=1 break fi @@ -182,7 +182,7 @@ discover_remote_laptop() { for ip in $ssh_hosts; do idx=$((idx + 1)) - if [[ "$ip" == "$gateway" ]]; then + if [[ $ip == "$gateway" ]]; then log_info "[$idx/$host_count] Skipping $ip (gateway)" continue fi @@ -198,13 +198,13 @@ discover_remote_laptop() { local has_sd has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true) - if [[ -n "$has_sd" ]]; then + if [[ -n $has_sd ]]; then log_success "[$idx/$host_count] $ip - Found SD card: $has_sd" found_laptop="$ip" break 2 else log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..." - if [[ -z "$found_laptop" ]]; then + if [[ -z $found_laptop ]]; then found_laptop="$ip" fi fi @@ -213,19 +213,19 @@ discover_remote_laptop() { done done - if [[ -z "$found_laptop" ]] || [[ -z "$found_user" ]]; then + if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then log_warning "No device with passwordless SSH found using common usernames." found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1) - if [[ -z "$found_laptop" ]]; then + if [[ -z $found_laptop ]]; then die "Could not find any suitable SSH-enabled device" fi log_info "Found SSH host at $found_laptop but need credentials." read -r -p "Enter username for $found_laptop: " found_user - if [[ -z "$found_user" ]]; then + if [[ -z $found_user ]]; then die "No username provided" fi fi @@ -279,16 +279,16 @@ download_raspberry_pi_os() { mkdir -p "$download_dir" - if [[ -f "$extracted_image" ]]; then + if [[ -f $extracted_image ]]; then log_info "Using existing image at $extracted_image" echo "$extracted_image" return fi - if [[ -f "$image_file" ]]; then + if [[ -f $image_file ]]; then local actual_size actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0) - if [[ "$actual_size" -lt "$expected_size" ]]; then + if [[ $actual_size -lt $expected_size ]]; then log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..." rm -f "$image_file" else @@ -296,7 +296,7 @@ download_raspberry_pi_os() { fi fi - if [[ ! -f "$image_file" ]]; then + if [[ ! -f $image_file ]]; then log_info "Downloading Raspberry Pi OS Lite (64-bit)..." log_info "This may take a while depending on your internet connection..." @@ -312,7 +312,7 @@ download_raspberry_pi_os() { local actual_size actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0) - if [[ "$actual_size" -lt "$expected_size" ]]; then + if [[ $actual_size -lt $expected_size ]]; then die "Download incomplete: got $actual_size bytes, expected $expected_size" fi log_success "Download complete: $actual_size bytes" @@ -321,7 +321,7 @@ download_raspberry_pi_os() { log_info "Extracting image..." xz -dk "$image_file" - if [[ ! -f "$extracted_image" ]]; then + if [[ ! -f $extracted_image ]]; then die "Failed to extract image" fi @@ -342,7 +342,7 @@ phase_flash_local() { local devices devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}') - if [[ -z "$devices" ]]; then + if [[ -z $devices ]]; then log_warning "No removable devices detected automatically." lsblk -d -o NAME,SIZE,TYPE,RM,TRAN read -r -p "Enter the SD card device path (e.g., /dev/sdb): " SD_CARD_DEVICE @@ -352,13 +352,13 @@ phase_flash_local() { read -r -p "Enter the SD card device path from above (e.g., /dev/sdb): " SD_CARD_DEVICE fi - if [[ ! -b "$SD_CARD_DEVICE" ]]; then + if [[ ! -b $SD_CARD_DEVICE ]]; then die "Device $SD_CARD_DEVICE does not exist or is not a block device" fi local root_device root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//') - if [[ "$SD_CARD_DEVICE" == "$root_device" ]]; then + if [[ $SD_CARD_DEVICE == "$root_device" ]]; then die "Cannot flash to the system drive!" fi @@ -375,7 +375,7 @@ phase_flash_local() { log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE" read -r -p "Are you sure you want to continue? (yes/no): " confirm - if [[ "$confirm" != "yes" ]]; then + if [[ $confirm != "yes" ]]; then die "Aborted by user" fi @@ -423,7 +423,7 @@ phase_flash_local() { root_partition="${SD_CARD_DEVICE}p2" fi - if [[ -n "$root_partition" ]]; then + if [[ -n $root_partition ]]; then local root_mount="/tmp/rpi-root" mkdir -p "$root_mount" mount "$root_partition" "$root_mount" @@ -475,7 +475,7 @@ phase_flash_remote() { local sd_device sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true) - if [[ -z "$sd_device" ]]; then + if [[ -z $sd_device ]]; then die "No SD card detected on remote laptop. Please insert an SD card and try again." fi @@ -530,7 +530,7 @@ phase_execute_remote() { log_info "=== Executing Flash on Remote Laptop ===" - if [[ -z "$SD_CARD_DEVICE" ]]; then + if [[ -z $SD_CARD_DEVICE ]]; then die "SD_CARD_DEVICE not set" fi @@ -570,7 +570,7 @@ phase_execute_remote() { touch "$boot_mount/ssh" log_success "SSH enabled" - if [[ -n "$encrypted_password" ]]; then + if [[ -n $encrypted_password ]]; then echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt" log_success "User '$PI_USER' configured" fi @@ -582,7 +582,7 @@ phase_execute_remote() { root_partition="${SD_CARD_DEVICE}p2" fi - if [[ -n "$root_partition" ]]; then + if [[ -n $root_partition ]]; then local root_mount="/tmp/rpi-root" mkdir -p "$root_mount" mount "$root_partition" "$root_mount" diff --git a/scripts/features/raspberry_pi_nextcloud.sh b/scripts/features/raspberry_pi_nextcloud.sh index 0bf53ce..e16f990 100755 --- a/scripts/features/raspberry_pi_nextcloud.sh +++ b/scripts/features/raspberry_pi_nextcloud.sh @@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf" # Load configuration from gitignored config file if it exists -if [[ -f "$CONFIG_FILE" ]]; then +if [[ -f $CONFIG_FILE ]]; then # shellcheck source=/dev/null source "$CONFIG_FILE" fi @@ -102,7 +102,7 @@ generate_password() { } auto_generate_nextcloud_password() { - if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then + if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20) log_info "Auto-generated Nextcloud admin password (will be saved to config file)" fi @@ -180,11 +180,11 @@ discover_raspberry_pi() { # Try resolving hostname directly pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true fi - if [[ -n "$pi_ip" ]]; then + if [[ -n $pi_ip ]]; then log_success "Found Pi by hostname: $pi_ip" echo "$pi_ip" return @@ -196,7 +196,7 @@ discover_raspberry_pi() { local ssh_hosts ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) || true - if [[ -z "$ssh_hosts" ]]; then + if [[ -z $ssh_hosts ]]; then die "No SSH-enabled devices found. Is the Pi connected and booted?" fi @@ -599,7 +599,7 @@ phase_fix_issues() { # Generate server certificate signed by our CA local regenerate="${1:-}" - if [[ ! -f "$ssl_dir/server.crt" ]] || [[ "$regenerate" == "--regenerate" ]]; then + if [[ ! -f "$ssl_dir/server.crt" ]] || [[ $regenerate == "--regenerate" ]]; then log_info "Generating server certificate signed by CA..." # Generate server private key @@ -718,7 +718,7 @@ EOF # Enable SVG in ImageMagick policy local policy_file="/etc/ImageMagick-6/policy.xml" - if [[ -f "$policy_file" ]]; then + if [[ -f $policy_file ]]; then # Remove SVG restrictions if present sed -i 's///' "$policy_file" # If no SVG policy exists, add one allowing it @@ -777,7 +777,7 @@ phase_setup_ssl() { log_info "=== Setting up Let's Encrypt SSL with DuckDNS ===" # Check if DuckDNS is configured - if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then + if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then echo log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain." log_info "1. Go to https://www.duckdns.org/ and sign in with Google/GitHub/etc." @@ -789,7 +789,7 @@ phase_setup_ssl() { read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN read -r -p "Enter your email (for Let's Encrypt notifications): " LETSENCRYPT_EMAIL - if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]] || [[ -z "$LETSENCRYPT_EMAIL" ]]; then + if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]] || [[ -z $LETSENCRYPT_EMAIL ]]; then die "All fields are required" fi fi @@ -817,7 +817,7 @@ phase_setup_ssl() { echo read -r -p "Have you set up port forwarding? (yes/no): " port_forward_done - if [[ "$port_forward_done" != "yes" ]]; then + if [[ $port_forward_done != "yes" ]]; then log_info "Please set up port forwarding and run this command again." log_info "Without port forwarding, Let's Encrypt cannot verify your domain." exit 0 @@ -829,7 +829,7 @@ phase_setup_ssl() { # When ip= is empty, DuckDNS auto-detects the public IP duckdns_response=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=") - if [[ "$duckdns_response" != "OK" ]]; then + if [[ $duckdns_response != "OK" ]]; then die "Failed to update DuckDNS: $duckdns_response" fi log_success "DuckDNS updated to public IP" @@ -855,14 +855,14 @@ DUCKEOF log_info "Waiting for DNS propagation (this may take a minute)..." local dns_ip="" local attempts=0 - while [[ "$dns_ip" != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do + while [[ $dns_ip != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do sleep 5 dns_ip=$(dig +short "$full_domain" 2>/dev/null | tail -1) || true attempts=$((attempts + 1)) log_info " DNS lookup: $dns_ip (expecting $public_ip, attempt $attempts/12)" done - if [[ "$dns_ip" != "$public_ip" ]]; then + if [[ $dns_ip != "$public_ip" ]]; then log_warning "DNS may not have propagated yet. Continuing anyway..." else log_success "DNS verified: $full_domain -> $public_ip" @@ -961,19 +961,19 @@ EOF phase_setup_ssl_remote() { log_info "=== Setting up Let's Encrypt SSL via SSH ===" - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then die "PI_PASSWORD not set. Run install-remote first." fi local pi_ip pi_ip=$(discover_raspberry_pi) - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then die "Failed to discover Raspberry Pi" fi # Get DuckDNS credentials if not set - if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then + if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then echo log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain." log_info "1. Go to https://www.duckdns.org/ and sign in" @@ -1012,14 +1012,14 @@ phase_setup_ssl_remote() { phase_install_remote() { log_info "=== Installing Nextcloud via SSH ===" - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then die "PI_PASSWORD not set. Did you run flash script first?" fi local pi_ip pi_ip=$(discover_raspberry_pi) - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then die "Failed to discover Raspberry Pi" fi @@ -1070,14 +1070,14 @@ phase_install_remote() { phase_install_ca() { log_info "=== Installing Nextcloud CA Certificate ===" - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then die "PI_PASSWORD not set. Run this after running install-remote or flash." fi local pi_ip pi_ip=$(discover_raspberry_pi) - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then die "Failed to discover Raspberry Pi" fi @@ -1089,7 +1089,7 @@ phase_install_ca() { sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no \ "${PI_USER}@${pi_ip}" "echo '$PI_PASSWORD' | sudo -S cat /etc/ssl/nextcloud/ca.crt" >"$ca_file" 2>/dev/null - if [[ ! -f "$ca_file" ]] || [[ ! -s "$ca_file" ]]; then + if [[ ! -f $ca_file ]] || [[ ! -s $ca_file ]]; then die "Failed to download CA certificate" fi @@ -1146,7 +1146,7 @@ phase_install_ca() { if [[ -d ~/.mozilla/firefox ]]; then local installed=0 for profile_dir in ~/.mozilla/firefox/*.default* ~/.mozilla/firefox/*.esr*; do - if [[ -d "$profile_dir" ]]; then + if [[ -d $profile_dir ]]; then if ! certutil -d sql:"$profile_dir" -L 2>/dev/null | grep -q "Nextcloud"; then certutil -d sql:"$profile_dir" -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null && installed=1 diff --git a/scripts/features/setup_activitywatch.sh b/scripts/features/setup_activitywatch.sh index b205075..6e91332 100755 --- a/scripts/features/setup_activitywatch.sh +++ b/scripts/features/setup_activitywatch.sh @@ -5,208 +5,206 @@ set -e # Exit on any error +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Function to check and request sudo privileges for package installation check_sudo() { - if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then - echo "Package installation requires sudo privileges." - echo "Requesting sudo access..." - exec sudo "$0" "$@" - fi + if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then + echo "Package installation requires sudo privileges." + echo "Requesting sudo access..." + exec sudo "$0" "$@" + fi } +# Get the actual user (even when running with sudo) +set_actual_user_vars + echo "ActivityWatch Setup for Arch Linux + i3" echo "=======================================" echo "Current Date: $(date)" -echo "User: ${SUDO_USER:-$USER}" - -# Get the actual user (even when running with sudo) -if [[ -n $SUDO_USER ]]; then - ACTUAL_USER="$SUDO_USER" - USER_HOME="/home/$SUDO_USER" -else - ACTUAL_USER="$USER" - USER_HOME="$HOME" -fi - +echo "User: $ACTUAL_USER" echo "Target user: $ACTUAL_USER" echo "User home: $USER_HOME" # Function to check if ActivityWatch is installed check_activitywatch_installed() { - echo "" - echo "1. Checking ActivityWatch Installation..." - echo "========================================" + echo "" + echo "1. Checking ActivityWatch Installation..." + echo "========================================" - # Check if activitywatch-bin is installed via pacman - if pacman -Qi activitywatch-bin &> /dev/null; then - echo "✓ activitywatch-bin package is installed" - return 0 - fi + # Check if activitywatch-bin is installed via pacman + if pacman -Qi activitywatch-bin &>/dev/null; then + echo "✓ activitywatch-bin package is installed" + return 0 + fi - # Check if aw-qt binary exists in common locations - local common_paths=( - "/usr/bin/aw-qt" - "/usr/local/bin/aw-qt" - "$USER_HOME/.local/bin/aw-qt" - "$USER_HOME/activitywatch/aw-qt" - ) + # Check if aw-qt binary exists in common locations + local common_paths=( + "/usr/bin/aw-qt" + "/usr/local/bin/aw-qt" + "$USER_HOME/.local/bin/aw-qt" + "$USER_HOME/activitywatch/aw-qt" + ) - for path in "${common_paths[@]}"; do - if [[ -x $path ]]; then - echo "✓ ActivityWatch found at: $path" - return 0 - fi - done + 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 + echo "✗ ActivityWatch not found" + return 1 } # Function to install ActivityWatch install_activitywatch() { - echo "" - echo "2. Installing ActivityWatch..." - echo "=============================" + echo "" + echo "2. Installing ActivityWatch..." + echo "=============================" - # Check if we need sudo for installation - check_sudo "install" + # Check if we need sudo for installation + check_sudo "install" - echo "Installing activitywatch-bin from AUR..." + echo "Installing activitywatch-bin from AUR..." - # Check if an AUR helper is available - local aur_helpers=("yay" "paru" "makepkg") - local helper_found="" + # Check if an AUR helper is available + local aur_helpers=("yay" "paru" "makepkg") + local helper_found="" - for helper in "${aur_helpers[@]}"; do - if command -v "$helper" &> /dev/null; then - helper_found="$helper" - break - fi - done + for helper in "${aur_helpers[@]}"; do + if command -v "$helper" &>/dev/null; then + helper_found="$helper" + break + fi + done - if [[ -n $helper_found && $helper_found != "makepkg" ]]; then - echo "Using AUR helper: $helper_found" - if [[ $EUID -eq 0 ]]; then - # Running as root, need to install as user - sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin - else - "$helper_found" -S --noconfirm activitywatch-bin - fi - else - echo "No AUR helper found. Installing manually with makepkg..." - install_activitywatch_manual - fi + 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" + echo "✓ ActivityWatch installation completed" } # Function to manually install ActivityWatch via makepkg install_activitywatch_manual() { - local temp_dir="/tmp/activitywatch-install" - local original_user="$ACTUAL_USER" + local temp_dir="/tmp/activitywatch-install" + local original_user="$ACTUAL_USER" - # Create temp directory - mkdir -p "$temp_dir" - cd "$temp_dir" + # Create temp directory + mkdir -p "$temp_dir" + cd "$temp_dir" - # Download PKGBUILD - if command -v git &> /dev/null; then - sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . - else - echo "Installing git..." - pacman -S --noconfirm git - sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . - fi + # Download PKGBUILD + if command -v git &>/dev/null; then + sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . + else + echo "Installing git..." + pacman -S --noconfirm git + sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git . + fi - # Build and install package - sudo -u "$original_user" makepkg -si --noconfirm + # Build and install package + sudo -u "$original_user" makepkg -si --noconfirm - # Cleanup - cd / - rm -rf "$temp_dir" + # Cleanup + cd / + rm -rf "$temp_dir" } # Function to check if ActivityWatch is running check_activitywatch_running() { - echo "" - echo "3. Checking ActivityWatch Status..." - echo "==================================" + echo "" + echo "3. Checking ActivityWatch Status..." + echo "==================================" - # Check for aw-qt process - if pgrep -f "aw-qt" > /dev/null; then - echo "✓ ActivityWatch (aw-qt) is running" - return 0 - fi + # Check for aw-qt process + if pgrep -f "aw-qt" >/dev/null; then + echo "✓ ActivityWatch (aw-qt) is running" + return 0 + fi - # Check for aw-server process - if pgrep -f "aw-server" > /dev/null; then - echo "✓ ActivityWatch server is running" - return 0 - fi + # Check for aw-server process + if pgrep -f "aw-server" >/dev/null; then + echo "✓ ActivityWatch server is running" + return 0 + fi - echo "✗ ActivityWatch is not running" - return 1 + echo "✗ ActivityWatch is not running" + return 1 } # Function to start ActivityWatch start_activitywatch() { - echo "" - echo "4. Starting ActivityWatch..." - echo "===========================" + echo "" + echo "4. Starting ActivityWatch..." + echo "===========================" - # Find aw-qt executable - local aw_qt_path="" + # Find aw-qt executable + local aw_qt_path="" - if command -v aw-qt &> /dev/null; then - aw_qt_path="$(which aw-qt)" - elif [[ -x "/usr/bin/aw-qt" ]]; then - aw_qt_path="/usr/bin/aw-qt" - else - echo "✗ Could not find aw-qt executable" - return 1 - fi + if command -v aw-qt &>/dev/null; then + aw_qt_path="$(which aw-qt)" + elif [[ -x "/usr/bin/aw-qt" ]]; then + aw_qt_path="/usr/bin/aw-qt" + else + echo "✗ Could not find aw-qt executable" + return 1 + fi - echo "Starting ActivityWatch as user: $ACTUAL_USER" - echo "Using aw-qt from: $aw_qt_path" + echo "Starting ActivityWatch as user: $ACTUAL_USER" + echo "Using aw-qt from: $aw_qt_path" - # Start as the actual user in the background - if [[ $EUID -eq 0 ]]; then - # Running as root, start as user - sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" & - else - # Running as user - "$aw_qt_path" & - fi + # Start as the actual user in the background + if [[ $EUID -eq 0 ]]; then + # Running as root, start as user + sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" & + else + # Running as user + "$aw_qt_path" & + fi - # Give it time to start - sleep 3 + # Give it time to start + sleep 3 - if check_activitywatch_running > /dev/null 2>&1; then - echo "✓ ActivityWatch started successfully" - else - echo "! ActivityWatch may be starting (check system tray)" - fi + if check_activitywatch_running >/dev/null 2>&1; then + echo "✓ ActivityWatch started successfully" + else + echo "! ActivityWatch may be starting (check system tray)" + fi } # Function to setup autostart setup_autostart() { - echo "" - echo "5. Setting Up Autostart..." - echo "=========================" + echo "" + echo "5. Setting Up Autostart..." + echo "=========================" - local autostart_dir="$USER_HOME/.config/autostart" - local desktop_file="$autostart_dir/activitywatch.desktop" - local i3_config="$USER_HOME/.config/i3/config" + local autostart_dir="$USER_HOME/.config/autostart" + local desktop_file="$autostart_dir/activitywatch.desktop" + local i3_config="$USER_HOME/.config/i3/config" - # Method 1: XDG Autostart (works with most desktop environments) - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir" - else - mkdir -p "$autostart_dir" - fi + # Method 1: XDG Autostart (works with most desktop environments) + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir" + else + mkdir -p "$autostart_dir" + fi - # Create desktop file for autostart - cat > "$desktop_file" << EOF + # Create desktop file for autostart + cat >"$desktop_file" <> '$i3_config' + # Method 2: i3 config autostart (specific to i3) + if [[ -f $i3_config ]]; then + # Check if autostart entry already exists + if ! grep -q "aw-qt" "$i3_config"; then + # Add autostart entry to i3 config + if [[ $EUID -eq 0 ]]; then + # Running as root + sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config' # Auto-start ActivityWatch exec --no-startup-id aw-qt EOF" - else - { - printf '\n' - printf '# Auto-start ActivityWatch\n' - printf 'exec --no-startup-id aw-qt\n' - } >> "$i3_config" - fi + else + { + printf '\n' + printf '# Auto-start ActivityWatch\n' + printf 'exec --no-startup-id aw-qt\n' + } >>"$i3_config" + fi - echo "✓ Added ActivityWatch to i3 config autostart" - else - echo "✓ ActivityWatch autostart already exists in i3 config" - fi - else - echo "! i3 config not found at $i3_config" - fi + echo "✓ Added ActivityWatch to i3 config autostart" + else + echo "✓ ActivityWatch autostart already exists in i3 config" + fi + else + echo "! i3 config not found at $i3_config" + fi } # Function to create i3blocks status script create_i3blocks_status() { - echo "" - echo "6. Creating i3blocks Status Script..." - echo "====================================" + echo "" + echo "6. Creating i3blocks Status Script..." + echo "====================================" - local i3blocks_dir="$USER_HOME/.config/i3blocks" - local status_script="$i3blocks_dir/activitywatch_status.sh" + local i3blocks_dir="$USER_HOME/.config/i3blocks" + local status_script="$i3blocks_dir/activitywatch_status.sh" - # Create i3blocks directory if it doesn't exist - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir" - else - mkdir -p "$i3blocks_dir" - fi + # Create i3blocks directory if it doesn't exist + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir" + else + mkdir -p "$i3blocks_dir" + fi - # Create the status script - cat > "$status_script" << 'EOF' + # Create the status script + cat >"$status_script" <<'EOF' #!/bin/bash # ActivityWatch status script for i3blocks # Shows ActivityWatch installation and running status @@ -325,134 +323,134 @@ else fi EOF - chmod +x "$status_script" + chmod +x "$status_script" - # Set proper ownership if running as root - if [[ $EUID -eq 0 ]]; then - chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script" - fi + # Set proper ownership if running as root + if [[ $EUID -eq 0 ]]; then + chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script" + fi - echo "✓ Created i3blocks status script: $status_script" + echo "✓ Created i3blocks status script: $status_script" - # Show configuration instructions - echo "" - echo "To add to your i3blocks config, add this block:" - echo "" - echo "[activitywatch]" - echo "command=~/.config/i3blocks/activitywatch_status.sh" - echo "interval=10" - echo "color=#FFFFFF" - echo "" + # Show configuration instructions + echo "" + echo "To add to your i3blocks config, add this block:" + echo "" + echo "[activitywatch]" + echo "command=~/.config/i3blocks/activitywatch_status.sh" + echo "interval=10" + echo "color=#FFFFFF" + echo "" } # Function to test the setup test_setup() { - echo "" - echo "7. Testing Setup..." - echo "==================" + echo "" + echo "7. Testing Setup..." + echo "==================" - echo "Installation status:" - if check_activitywatch_installed > /dev/null 2>&1; then - echo "✓ ActivityWatch is installed" - else - echo "✗ ActivityWatch is not installed" - fi + echo "Installation status:" + if check_activitywatch_installed >/dev/null 2>&1; then + echo "✓ ActivityWatch is installed" + else + echo "✗ ActivityWatch is not installed" + fi - echo "Running status:" - if check_activitywatch_running > /dev/null 2>&1; then - echo "✓ ActivityWatch is running" - else - echo "✗ ActivityWatch is not running" - fi + echo "Running status:" + if check_activitywatch_running >/dev/null 2>&1; then + echo "✓ ActivityWatch is running" + else + echo "✗ ActivityWatch is not running" + fi - echo "Autostart files:" - if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then - echo "✓ XDG autostart file exists" - else - echo "✗ XDG autostart file missing" - fi + echo "Autostart files:" + if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then + echo "✓ XDG autostart file exists" + else + echo "✗ XDG autostart file missing" + fi - if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then - echo "✓ i3 autostart configured" - else - echo "! i3 autostart may not be configured" - fi + if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then + echo "✓ i3 autostart configured" + else + echo "! i3 autostart may not be configured" + fi - echo "i3blocks status script:" - if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then - echo "✓ i3blocks status script created" - echo "Testing status script:" - if [[ $EUID -eq 0 ]]; then - sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh" - else - "$USER_HOME/.config/i3blocks/activitywatch_status.sh" - fi - else - echo "✗ i3blocks status script missing" - fi + echo "i3blocks status script:" + if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then + echo "✓ i3blocks status script created" + echo "Testing status script:" + if [[ $EUID -eq 0 ]]; then + sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh" + else + "$USER_HOME/.config/i3blocks/activitywatch_status.sh" + fi + else + echo "✗ i3blocks status script missing" + fi } # Function to show final instructions show_instructions() { - echo "" - echo "==========================================" - echo "ActivityWatch Setup Complete" - echo "==========================================" - echo "Summary:" - echo "✓ ActivityWatch installation checked/completed" - echo "✓ ActivityWatch startup configured" - echo "✓ Autostart configured (XDG + i3)" - echo "✓ i3blocks status script created" - echo "" - echo "Next steps:" - echo "1. Add the i3blocks configuration to your config file:" - echo " ~/.config/i3blocks/config" - echo "" - echo "2. Reload i3 configuration:" - echo " Super+Shift+R" - echo "" - echo "3. ActivityWatch web interface should be available at:" - echo " http://localhost:5600" - echo "" - echo "4. Check system tray for ActivityWatch icon" - echo "" - echo "Files created:" - echo " ~/.config/autostart/activitywatch.desktop" - echo " ~/.config/i3blocks/activitywatch_status.sh" - echo " ~/.config/i3/config (modified)" - echo "" + echo "" + echo "==========================================" + echo "ActivityWatch Setup Complete" + echo "==========================================" + echo "Summary:" + echo "✓ ActivityWatch installation checked/completed" + echo "✓ ActivityWatch startup configured" + echo "✓ Autostart configured (XDG + i3)" + echo "✓ i3blocks status script created" + echo "" + echo "Next steps:" + echo "1. Add the i3blocks configuration to your config file:" + echo " ~/.config/i3blocks/config" + echo "" + echo "2. Reload i3 configuration:" + echo " Super+Shift+R" + echo "" + echo "3. ActivityWatch web interface should be available at:" + echo " http://localhost:5600" + echo "" + echo "4. Check system tray for ActivityWatch icon" + echo "" + echo "Files created:" + echo " ~/.config/autostart/activitywatch.desktop" + echo " ~/.config/i3blocks/activitywatch_status.sh" + echo " ~/.config/i3/config (modified)" + echo "" } # Main execution flow main() { - local need_install=false - local need_start=false + local need_install=false + local need_start=false - # Check installation - if ! check_activitywatch_installed; then - need_install=true - fi + # Check installation + if ! check_activitywatch_installed; then + need_install=true + fi - # Install if needed - if [[ $need_install == true ]]; then - install_activitywatch - fi + # Install if needed + if [[ $need_install == true ]]; then + install_activitywatch + fi - # Check if running - if ! check_activitywatch_running; then - need_start=true - fi + # Check if running + if ! check_activitywatch_running; then + need_start=true + fi - # Start if needed - if [[ $need_start == true ]]; then - start_activitywatch - fi + # Start if needed + if [[ $need_start == true ]]; then + start_activitywatch + fi - # Always set up autostart and i3blocks (in case they're missing) - setup_autostart - create_i3blocks_status - test_setup - show_instructions + # Always set up autostart and i3blocks (in case they're missing) + setup_autostart + create_i3blocks_status + test_setup + show_instructions } # Run main function diff --git a/scripts/features/setup_nextcloud_raspberry.sh b/scripts/features/setup_nextcloud_raspberry.sh index b2f5ab0..d14cc27 100644 --- a/scripts/features/setup_nextcloud_raspberry.sh +++ b/scripts/features/setup_nextcloud_raspberry.sh @@ -19,7 +19,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CONFIG_FILE="${SCRIPT_DIR}/.nextcloud_raspberry.conf" # Load configuration from gitignored config file if it exists -if [[ -f "$CONFIG_FILE" ]]; then +if [[ -f $CONFIG_FILE ]]; then # shellcheck source=/dev/null source "$CONFIG_FILE" fi @@ -108,14 +108,14 @@ generate_password() { } auto_generate_pi_password() { - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then PI_PASSWORD=$(generate_password 16) log_info "Auto-generated Pi password (will be saved to config file)" fi } auto_generate_nextcloud_password() { - if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then + if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20) log_info "Auto-generated Nextcloud admin password (will be saved to config file)" fi @@ -133,8 +133,8 @@ prompt_password() { read -r -s -p "Confirm password: " password_confirm echo - if [[ "$password" == "$password_confirm" ]]; then - if [[ -z "$password" ]]; then + if [[ $password == "$password_confirm" ]]; then + if [[ -z $password ]]; then log_warning "Password cannot be empty. Please try again." continue fi @@ -157,7 +157,7 @@ detect_sd_card() { local devices devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}') - if [[ -z "$devices" ]]; then + if [[ -z $devices ]]; then log_warning "No removable devices detected automatically." log_info "Available block devices:" lsblk -d -o NAME,SIZE,TYPE,RM,TRAN @@ -171,14 +171,14 @@ detect_sd_card() { fi # Validate device exists - if [[ ! -b "$SD_CARD_DEVICE" ]]; then + if [[ ! -b $SD_CARD_DEVICE ]]; then die "Device $SD_CARD_DEVICE does not exist or is not a block device" fi # Safety check - don't flash system drive local root_device root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//') - if [[ "$SD_CARD_DEVICE" == "$root_device" ]]; then + if [[ $SD_CARD_DEVICE == "$root_device" ]]; then die "Cannot flash to the system drive!" fi } @@ -192,17 +192,17 @@ download_raspberry_pi_os() { mkdir -p "$download_dir" - if [[ -f "$extracted_image" ]]; then + if [[ -f $extracted_image ]]; then log_info "Using existing image at $extracted_image" echo "$extracted_image" return fi # Check if download exists and is complete - if [[ -f "$image_file" ]]; then + if [[ -f $image_file ]]; then local actual_size actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0) - if [[ "$actual_size" -lt "$expected_size" ]]; then + if [[ $actual_size -lt $expected_size ]]; then log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..." rm -f "$image_file" else @@ -210,7 +210,7 @@ download_raspberry_pi_os() { fi fi - if [[ ! -f "$image_file" ]]; then + if [[ ! -f $image_file ]]; then log_info "Downloading Raspberry Pi OS Lite (64-bit)..." log_info "This may take a while depending on your internet connection..." @@ -229,7 +229,7 @@ download_raspberry_pi_os() { # Verify download size local actual_size actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0) - if [[ "$actual_size" -lt "$expected_size" ]]; then + if [[ $actual_size -lt $expected_size ]]; then die "Download incomplete: got $actual_size bytes, expected $expected_size" fi log_success "Download complete: $actual_size bytes" @@ -238,7 +238,7 @@ download_raspberry_pi_os() { log_info "Extracting image..." xz -dk "$image_file" - if [[ ! -f "$extracted_image" ]]; then + if [[ ! -f $extracted_image ]]; then die "Failed to extract image" fi @@ -251,7 +251,7 @@ flash_sd_card() { log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE" read -r -p "Are you sure you want to continue? (yes/no): " confirm - if [[ "$confirm" != "yes" ]]; then + if [[ $confirm != "yes" ]]; then die "Aborted by user" fi @@ -300,7 +300,7 @@ configure_headless_boot() { # Configure WiFi (optional) read -r -p "Do you want to configure WiFi? (y/n): " configure_wifi - if [[ "$configure_wifi" == "y" ]]; then + if [[ $configure_wifi == "y" ]]; then read -r -p "WiFi SSID: " wifi_ssid read -r -s -p "WiFi Password: " wifi_password echo @@ -320,7 +320,7 @@ EOF fi # Create userconf.txt for first user (Raspberry Pi OS Bookworm+) - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then prompt_password "Enter password for Pi user '$PI_USER'" PI_PASSWORD fi @@ -337,7 +337,7 @@ EOF root_partition="${SD_CARD_DEVICE}p2" fi - if [[ -n "$root_partition" ]]; then + if [[ -n $root_partition ]]; then local root_mount="/tmp/rpi-root" mkdir -p "$root_mount" mount "$root_partition" "$root_mount" @@ -474,7 +474,7 @@ discover_remote_laptop() { # Extract IPs from nmap output - grep for report lines then extract IP ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) - if [[ -z "$ssh_hosts" ]]; then + if [[ -z $ssh_hosts ]]; then die "No SSH-enabled devices found on network" fi @@ -489,7 +489,7 @@ discover_remote_laptop() { for u in "${common_users[@]}"; do local is_dup=0 for existing in "${users[@]}"; do - if [[ "$u" == "$existing" ]]; then + if [[ $u == "$existing" ]]; then is_dup=1 break fi @@ -510,7 +510,7 @@ discover_remote_laptop() { idx=$((idx + 1)) # Skip gateway - if [[ "$ip" == "$gateway" ]]; then + if [[ $ip == "$gateway" ]]; then log_info "[$idx/$host_count] Skipping $ip (gateway)" continue fi @@ -528,13 +528,13 @@ discover_remote_laptop() { local has_sd has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true) - if [[ -n "$has_sd" ]]; then + if [[ -n $has_sd ]]; then log_success "[$idx/$host_count] $ip - Found SD card: $has_sd" found_laptop="$ip" break 2 # Break out of both loops else log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..." - if [[ -z "$found_laptop" ]]; then + if [[ -z $found_laptop ]]; then found_laptop="$ip" fi fi @@ -542,26 +542,26 @@ discover_remote_laptop() { fi done - if [[ -z "$found_user" ]]; then + if [[ -z $found_user ]]; then log_info "[$idx/$host_count] $ip - No SSH key access with any common username" fi done # If no passwordless access found, prompt user for username - if [[ -z "$found_laptop" ]] || [[ -z "$found_user" ]]; then + if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then log_warning "No device with passwordless SSH found using common usernames." # Pick first available SSH host found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1) - if [[ -z "$found_laptop" ]]; then + if [[ -z $found_laptop ]]; then die "Could not find any suitable SSH-enabled device" fi log_info "Found SSH host at $found_laptop but need credentials." read -r -p "Enter username for $found_laptop: " found_user - if [[ -z "$found_user" ]]; then + if [[ -z $found_user ]]; then die "No username provided" fi fi @@ -596,7 +596,7 @@ phase_flash_remote() { local sd_device sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true) - if [[ -z "$sd_device" ]]; then + if [[ -z $sd_device ]]; then die "No SD card detected on remote laptop. Please insert an SD card and try again." fi @@ -657,7 +657,7 @@ phase_flash_remote_execute() { log_info "=== Executing Flash on Remote Laptop ===" - if [[ -z "$SD_CARD_DEVICE" ]]; then + if [[ -z $SD_CARD_DEVICE ]]; then die "SD_CARD_DEVICE not set" fi @@ -703,7 +703,7 @@ phase_flash_remote_execute() { log_success "SSH enabled" # Create userconf.txt for first user - if [[ -n "$encrypted_password" ]]; then + if [[ -n $encrypted_password ]]; then echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt" log_success "User '$PI_USER' configured" fi @@ -716,7 +716,7 @@ phase_flash_remote_execute() { root_partition="${SD_CARD_DEVICE}p2" fi - if [[ -n "$root_partition" ]]; then + if [[ -n $root_partition ]]; then local root_mount="/tmp/rpi-root" mkdir -p "$root_mount" mount "$root_partition" "$root_mount" @@ -951,7 +951,7 @@ download_nextcloud() { local download_dir="/tmp" local nc_zip="$download_dir/nextcloud.zip" - if [[ -f "$nc_zip" ]]; then + if [[ -f $nc_zip ]]; then log_info "Nextcloud archive already downloaded" else wget -O "$nc_zip" "$nc_url" @@ -1069,7 +1069,7 @@ install_nextcloud() { local db_password db_password=$(cat /root/.nextcloud_db_password) - if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then + if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then prompt_password "Enter Nextcloud admin password" NEXTCLOUD_ADMIN_PASSWORD fi @@ -1206,11 +1206,11 @@ discover_raspberry_pi() { # Try resolving hostname directly pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true fi - if [[ -n "$pi_ip" ]]; then + if [[ -n $pi_ip ]]; then log_success "Found Pi by hostname: $pi_ip" echo "$pi_ip" return @@ -1224,7 +1224,7 @@ discover_raspberry_pi() { local ssh_hosts ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | grep -vw "$REMOTE_LAPTOP_IP" 2>/dev/null | sort -u) || true - if [[ -z "$ssh_hosts" ]]; then + if [[ -z $ssh_hosts ]]; then die "No new SSH-enabled devices found. Is the Pi connected and booted?" fi @@ -1259,14 +1259,14 @@ phase_all_remote() { local pi_ip pi_ip=$(discover_raspberry_pi) - if [[ -z "$pi_ip" ]]; then + if [[ -z $pi_ip ]]; then die "Failed to discover Raspberry Pi" fi log_info "Using Raspberry Pi at: $pi_ip" # PI_PASSWORD should already be set from config file - if [[ -z "$PI_PASSWORD" ]]; then + if [[ -z $PI_PASSWORD ]]; then die "PI_PASSWORD not set. Did you run flash-remote first?" fi diff --git a/scripts/fixes/fix_virtualbox.sh b/scripts/fixes/fix_virtualbox.sh index f60efec..dad8ad8 100644 --- a/scripts/fixes/fix_virtualbox.sh +++ b/scripts/fixes/fix_virtualbox.sh @@ -2,195 +2,182 @@ set -euo pipefail +# Source common library +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + on_error() { - local exit_code=$? - local line_number=$1 - printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2 + local exit_code=$? + local line_number=$1 + log_error "Unexpected failure at line ${line_number} (exit code ${exit_code})." } trap 'on_error ${LINENO}' ERR -log_info() { - printf '\033[1;34m[INFO]\033[0m %s\n' "$*" -} - -log_warn() { - printf '\033[1;33m[WARN]\033[0m %s\n' "$*" -} - -log_error() { - printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 - exit 1 -} - -require_root() { - if [[ ${EUID} -ne 0 ]]; then - log_error "This script must be run as root (try again with sudo)." - fi -} - require_pacman() { - if ! command -v pacman > /dev/null 2>&1; then - log_error "pacman not found. This script is intended for Arch Linux systems." - fi + if ! has_cmd pacman; then + log_error "pacman not found. This script is intended for Arch Linux systems." + exit 1 + fi } detect_kernel_release() { - uname -r + uname -r } select_host_package() { - local kernel_release=$1 - case "${kernel_release}" in - *-lts) - echo "virtualbox-host-modules-lts" - ;; - *-arch*) - echo "virtualbox-host-modules-arch" - ;; - *) - echo "virtualbox-host-dkms" - ;; - esac + local kernel_release=$1 + case "${kernel_release}" in + *-lts) + echo "virtualbox-host-modules-lts" + ;; + *-arch*) + echo "virtualbox-host-modules-arch" + ;; + *) + echo "virtualbox-host-dkms" + ;; + esac } collect_kernel_headers() { - local -a headers=() - local kernel_pkg header_pkg - for kernel_pkg in linux linux-lts linux-zen linux-hardened; do - if pacman -Q "${kernel_pkg}" > /dev/null 2>&1; then - header_pkg="${kernel_pkg}-headers" - headers+=("${header_pkg}") - fi - done - if [[ ${#headers[@]} -gt 0 ]]; then - printf '%s\n' "${headers[@]}" - fi + local -a headers=() + local kernel_pkg header_pkg + for kernel_pkg in linux linux-lts linux-zen linux-hardened; do + if pacman -Q "${kernel_pkg}" >/dev/null 2>&1; then + header_pkg="${kernel_pkg}-headers" + headers+=("${header_pkg}") + fi + done + if [[ ${#headers[@]} -gt 0 ]]; then + printf '%s\n' "${headers[@]}" + fi } maybe_remove_conflicting_host_packages() { - local selected_package=$1 - local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") - local pkg - for pkg in "${candidates[@]}"; do - if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" > /dev/null 2>&1; then - log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." - pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" - fi - done + local selected_package=$1 + local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts") + local pkg + for pkg in "${candidates[@]}"; do + if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" >/dev/null 2>&1; then + log_warn "Removing conflicting package ${pkg} before installing ${selected_package}." + pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}" + fi + done } install_packages() { - local -a packages=() - local -a headers=() - local host_package=$1 - shift - if [[ $# -gt 0 ]]; then - mapfile -t headers < <(printf '%s\n' "$@" | sort -u) - fi - packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") - if [[ ${host_package} == "virtualbox-host-dkms" ]]; then - packages+=("dkms") - fi - if [[ ${#headers[@]} -gt 0 ]]; then - packages+=("${headers[@]}") - fi - log_info "Installing packages: ${packages[*]}" - pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" + local -a packages=() + local -a headers=() + local host_package=$1 + shift + if [[ $# -gt 0 ]]; then + mapfile -t headers < <(printf '%s\n' "$@" | sort -u) + fi + packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}") + if [[ ${host_package} == "virtualbox-host-dkms" ]]; then + packages+=("dkms") + fi + if [[ ${#headers[@]} -gt 0 ]]; then + packages+=("${headers[@]}") + fi + log_info "Installing packages: ${packages[*]}" + pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}" } rebuild_virtualbox_modules() { - local host_package=$1 - if [[ ${host_package} == "virtualbox-host-dkms" ]]; then - if command -v dkms > /dev/null 2>&1; then - log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." - dkms autoinstall - else - log_warn "dkms command not found; skipping DKMS rebuild." - fi - fi + local host_package=$1 + if [[ ${host_package} == "virtualbox-host-dkms" ]]; then + if command -v dkms >/dev/null 2>&1; then + log_info "Rebuilding VirtualBox DKMS modules for all installed kernels." + dkms autoinstall + else + log_warn "dkms command not found; skipping DKMS rebuild." + fi + fi } reload_virtualbox_modules() { - log_info "Loading VirtualBox kernel modules." - if [[ -x /sbin/rcvboxdrv ]]; then - /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." - elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then - /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." - fi + log_info "Loading VirtualBox kernel modules." + if [[ -x /sbin/rcvboxdrv ]]; then + /sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules." + elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then + /usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules." + fi - local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) - local mod - for mod in "${modules[@]}"; do - if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then - if ! modprobe "${mod}" > /dev/null 2>&1; then - log_warn "Module ${mod} failed to load; check dmesg for details." - fi - fi - done + local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci) + local mod + for mod in "${modules[@]}"; do + if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then + if ! modprobe "${mod}" >/dev/null 2>&1; then + log_warn "Module ${mod} failed to load; check dmesg for details." + fi + fi + done - if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then - log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." - fi - log_info "VirtualBox kernel driver loaded successfully." + if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then + log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues." + fi + log_info "VirtualBox kernel driver loaded successfully." } warn_if_secure_boot_enabled() { - local secure_boot_file - if [[ -d /sys/firmware/efi/efivars ]]; then - secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2> /dev/null || true) - if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then - local state - state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2> /dev/null || echo "0") - if [[ ${state} == "1" ]]; then - log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." - fi - fi - fi + local secure_boot_file + if [[ -d /sys/firmware/efi/efivars ]]; then + secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2>/dev/null || true) + if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then + local state + state=$(hexdump -n 1 -s 4 -e '1 "%d"' "${secure_boot_file}" 2>/dev/null || echo "0") + if [[ ${state} == "1" ]]; then + log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually." + fi + fi + fi } remind_group_membership() { - local invoking_user=${SUDO_USER:-} - if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then - if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then - log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" - else - log_info "User ${invoking_user} is already in the vboxusers group." - fi - fi + local invoking_user=${SUDO_USER:-} + if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then + if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then + log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers" + else + log_info "User ${invoking_user} is already in the vboxusers group." + fi + fi } main() { - require_root - require_pacman + require_root + require_pacman - PACMAN_INSTALL_FLAGS=(--needed) - PACMAN_REMOVE_FLAGS=() - if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then - log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." - else - PACMAN_INSTALL_FLAGS+=(--noconfirm) - PACMAN_REMOVE_FLAGS+=(--noconfirm) - fi + PACMAN_INSTALL_FLAGS=(--needed) + PACMAN_REMOVE_FLAGS=() + if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then + log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation." + else + PACMAN_INSTALL_FLAGS+=(--noconfirm) + PACMAN_REMOVE_FLAGS+=(--noconfirm) + fi - local kernel_release host_package - kernel_release=$(detect_kernel_release) - log_info "Detected running kernel: ${kernel_release}" - host_package=$(select_host_package "${kernel_release}") - log_info "Selected VirtualBox host package: ${host_package}" + local kernel_release host_package + kernel_release=$(detect_kernel_release) + log_info "Detected running kernel: ${kernel_release}" + host_package=$(select_host_package "${kernel_release}") + log_info "Selected VirtualBox host package: ${host_package}" - mapfile -t kernel_headers < <(collect_kernel_headers) - if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then - log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." - fi + mapfile -t kernel_headers < <(collect_kernel_headers) + if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then + log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules." + fi - maybe_remove_conflicting_host_packages "${host_package}" - install_packages "${host_package}" "${kernel_headers[@]}" - rebuild_virtualbox_modules "${host_package}" - reload_virtualbox_modules - warn_if_secure_boot_enabled - remind_group_membership + maybe_remove_conflicting_host_packages "${host_package}" + install_packages "${host_package}" "${kernel_headers[@]}" + rebuild_virtualbox_modules "${host_package}" + reload_virtualbox_modules + warn_if_secure_boot_enabled + remind_group_membership - log_info "VirtualBox installation and driver setup complete." + log_info "VirtualBox installation and driver setup complete." } main "$@" diff --git a/scripts/fixes/nvidia_troubleshoot.sh b/scripts/fixes/nvidia_troubleshoot.sh index 9ab8b7c..e4e72f8 100755 --- a/scripts/fixes/nvidia_troubleshoot.sh +++ b/scripts/fixes/nvidia_troubleshoot.sh @@ -17,16 +17,7 @@ shift "$COMMON_ARGS_SHIFT" # Check for sudo privileges require_root "$@" -echo "NVIDIA Comprehensive Troubleshooter & GSP Disabler" -echo "==================================================" -echo "Current Date: $(date)" -echo "User: $USER" -echo "Original user: $(get_actual_user)" -if [[ $INTERACTIVE_MODE == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" -else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" -fi +print_setup_header "NVIDIA Comprehensive Troubleshooter & GSP Disabler" # Check if nvidia module is loaded if ! lsmod | grep -q nvidia; then diff --git a/scripts/lib/android.sh b/scripts/lib/android.sh new file mode 100644 index 0000000..6091aad --- /dev/null +++ b/scripts/lib/android.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Shared functions for Android-related scripts +# Source this file after sourcing common.sh + +# Prevent multiple sourcing +[[ -n ${_LIB_ANDROID_LOADED:-} ]] && return 0 +_LIB_ANDROID_LOADED=1 + +ANDROID_WORK_DIR="${HOME}/.cache/android-adblock" +ensure_dir "$ANDROID_WORK_DIR" + +# Exit with error message +die() { + echo "[ERROR] $*" >&2 + exit 1 +} + +# Print section header +print_header() { + echo + echo "========================================" + echo " $1" + echo "========================================" + echo +} + +# Check if ADB device is connected +check_adb_device() { + log "Checking device connection..." + if ! adb devices | grep -q "device$"; then + die "No device connected. Enable USB debugging and connect your phone." + fi + log "Device connected" +} + +# Check if device has root access +check_adb_root() { + log "Checking root access..." + if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then + die "Root access not available. Make sure Magisk is installed and grant root to Shell." + fi + log "Root access confirmed" +} + +# Re-exec with sudo if needed to read /etc/hosts +require_hosts_readable() { + if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then + exec sudo -E bash "$0" "$@" + fi +} diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index dedcdba..a735990 100644 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -21,7 +21,7 @@ log_message() { local formatted formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg" echo "$formatted" >&2 - if [[ -n "$log_file" ]]; then + if [[ -n $log_file ]]; then echo "$formatted" >>"$log_file" 2>/dev/null || true fi } @@ -57,13 +57,23 @@ get_actual_user() { get_actual_user_home() { local user user=$(get_actual_user) - if [[ "$user" == "root" ]]; then + if [[ $user == "root" ]]; then echo "/root" else echo "/home/$user" fi } +# Set both ACTUAL_USER and USER_HOME variables (common pattern) +# Usage: set_actual_user_vars +# echo "$ACTUAL_USER" # => the actual user +# echo "$USER_HOME" # => /home/username +set_actual_user_vars() { + ACTUAL_USER=$(get_actual_user) + USER_HOME=$(get_actual_user_home) + export ACTUAL_USER USER_HOME +} + # ============================================================================= # ARGUMENT PARSING HELPERS # ============================================================================= @@ -102,6 +112,32 @@ parse_interactive_args() { done } +# Handle common argument patterns for scripts with custom usage functions +# Usage: handle_arg_help_or_unknown "$1" usage_function err_function +# Returns: 0 if argument was handled (caller should continue), 1 if not our concern +# Exits: on -h/--help (exit 0) or unknown arg starting with - (exit 2) +handle_arg_help_or_unknown() { + local arg="$1" + local usage_fn="${2:-usage}" + local err_fn="${3:-err}" + + case "$arg" in + -h | --help) + "$usage_fn" + exit 0 + ;; + -*) + "$err_fn" "Unknown argument: $arg" + "$usage_fn" + exit 2 + ;; + *) + return 1 # Not a flag, let caller handle it + ;; + esac + return 0 +} + # ============================================================================= # FOCUS APP DETECTION (for digital wellbeing scripts) # ============================================================================= @@ -172,6 +208,61 @@ require_command() { return 0 } +# Check for ImageMagick and display helpful installation message +# Usage: require_imagemagick [optional: "magick" or "convert"] +# Returns: Sets MAGICK_CMD variable to available command +require_imagemagick() { + local preferred="${1:-}" + + if [[ $preferred == "magick" ]] || [[ -z $preferred ]]; then + if command -v magick &>/dev/null; then + MAGICK_CMD="magick" + export MAGICK_CMD + return 0 + fi + fi + + if [[ $preferred == "convert" ]] || [[ -z $preferred ]]; then + if command -v convert &>/dev/null; then + MAGICK_CMD="convert" + export MAGICK_CMD + return 0 + fi + fi + + echo "Error: ImageMagick is not installed." >&2 + echo "Install it with:" >&2 + echo " Arch Linux: sudo pacman -S imagemagick" >&2 + echo " Ubuntu/Debian: sudo apt install imagemagick" >&2 + return 1 +} + +# Install missing pacman packages +# Usage: install_missing_pacman_packages pkg1 pkg2 pkg3 ... +# Returns 0 if all packages installed successfully, 1 otherwise +install_missing_pacman_packages() { + local packages=("$@") + local missing=() + + for pkg in "${packages[@]}"; do + if ! pacman -Qi "$pkg" >/dev/null 2>&1; then + missing+=("$pkg") + fi + done + + if [[ ${#missing[@]} -eq 0 ]]; then + echo "[INFO] All required packages are already installed." + return 0 + fi + + echo "[INFO] Installing missing packages: ${missing[*]}" + if ! sudo pacman -S --needed --noconfirm "${missing[@]}"; then + echo "[ERROR] Failed to install packages" >&2 + return 1 + fi + return 0 +} + # ============================================================================= # NOTIFICATION # ============================================================================= @@ -203,7 +294,7 @@ get_script_dir() { # Usage: ensure_dir "/path/to/dir" ensure_dir() { local dir="$1" - if [[ ! -d "$dir" ]]; then + if [[ ! -d $dir ]]; then mkdir -p "$dir" fi } @@ -212,30 +303,176 @@ ensure_dir() { # SYSTEMD HELPERS # ============================================================================= +# Internal helper for running systemctl with optional --user flag +_systemctl_cmd() { + local user_flag="$1" + shift + if [[ $user_flag == "--user" ]]; then + systemctl --user "$@" + else + systemctl "$@" + fi +} + # Enable and start a systemd service (user or system) # Usage: enable_service "service-name" [--user] enable_service() { local service="$1" local user_flag="${2:-}" - - if [[ "$user_flag" == "--user" ]]; then - systemctl --user daemon-reload - systemctl --user enable --now "$service" - else - systemctl daemon-reload - systemctl enable --now "$service" - fi + _systemctl_cmd "$user_flag" daemon-reload + _systemctl_cmd "$user_flag" enable --now "$service" } # Check if a systemd service is active # Usage: if is_service_active "service-name" [--user]; then ... is_service_active() { - local service="$1" - local user_flag="${2:-}" + _systemctl_cmd "${2:-}" is-active --quiet "$1" +} - if [[ "$user_flag" == "--user" ]]; then - systemctl --user is-active --quiet "$service" +# Check if a systemd service is enabled +# Usage: if is_service_enabled "service-name" [--user]; then ... +is_service_enabled() { + _systemctl_cmd "${2:-}" is-enabled --quiet "$1" 2>/dev/null +} + +# ============================================================================= +# COLORED LOGGING (for scripts that need colored output) +# ============================================================================= + +# ANSI color codes +declare -g COLOR_RED='\033[1;31m' +declare -g COLOR_GREEN='\033[1;32m' +declare -g COLOR_YELLOW='\033[1;33m' +declare -g COLOR_BLUE='\033[1;34m' +declare -g COLOR_NC='\033[0m' + +log_info() { + printf "${COLOR_BLUE}[INFO]${COLOR_NC} %s\n" "$*" +} + +log_ok() { + printf "${COLOR_GREEN}[ OK ]${COLOR_NC} %s\n" "$*" +} + +log_warn() { + printf "${COLOR_YELLOW}[WARN]${COLOR_NC} %s\n" "$*" >&2 +} + +log_error() { + printf "${COLOR_RED}[ERROR]${COLOR_NC} %s\n" "$*" >&2 +} + +# Alias for compatibility +warn() { log_warn "$@"; } +err() { log_error "$@"; } + +# ============================================================================= +# INTERACTIVE PROMPTS +# ============================================================================= + +# Ask yes/no question, returns 0 for yes, 1 for no +# Usage: if ask_yes_no "Continue?"; then ... +ask_yes_no() { + local prompt="$1" + local ans + read -r -p "$prompt [y/N]: " ans || true + case "${ans:-}" in + y | Y | yes | YES) return 0 ;; + *) return 1 ;; + esac +} + +# Check if a command is available +# Usage: if has_cmd git; then ... +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +# ============================================================================= +# STANDARD SETUP HEADER +# ============================================================================= + +# Print a standard setup header for scripts +# Usage: print_setup_header "Script Name" +print_setup_header() { + local title="$1" + echo "$title" + printf '=%.0s' $(seq 1 ${#title}) + echo "" + echo "Current Date: $(date)" + echo "User: $USER" + echo "Original user: $(get_actual_user)" + if [[ $INTERACTIVE_MODE == "true" ]]; then + echo "Mode: Interactive (prompts enabled)" else - systemctl is-active --quiet "$service" + echo "Mode: Automatic (auto-yes, use --interactive for prompts)" fi } + +# ============================================================================= +# MOUNT/UNMOUNT HELPERS (for hosts guard and similar) +# ============================================================================= + +# Count mount layers for a path +# Usage: count=$(mount_layers_count "/etc/hosts") +mount_layers_count() { + local target="$1" + awk -v t="$target" '$5==t{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0 +} + +# Collapse all bind mount layers for a path +# Usage: collapse_mounts "/etc/hosts" [max_iterations] +collapse_mounts() { + local target="$1" + local max_iter="${2:-20}" + local i=0 + + if has_cmd mountpoint; then + while mountpoint -q "$target"; do + umount -l "$target" >/dev/null 2>&1 || break + i=$((i + 1)) + ((i >= max_iter)) && break + done + else + local cnt + cnt=$(mount_layers_count "$target") + while ((cnt > 1)); do + umount -l "$target" >/dev/null 2>&1 || break + i=$((i + 1)) + ((i >= max_iter)) && break + cnt=$(mount_layers_count "$target") + done + fi +} + +# ============================================================================= +# RESOLUTION/FORMAT VALIDATION +# ============================================================================= + +# Validate resolution format (WIDTHxHEIGHT) +# Usage: if validate_resolution "1920x1080"; then ... +validate_resolution() { + local res="$1" + [[ $res =~ ^[0-9]+x[0-9]+$ ]] +} + +# Generate output filename with suffix +# Usage: output=$(generate_output_filename "input.jpg" "_resized") +generate_output_filename() { + local input="$1" + local suffix="$2" + local ext="${3:-}" + + local basename dirname filename extension + basename=$(basename "$input") + dirname=$(dirname "$input") + filename="${basename%.*}" + extension="${basename##*.}" + + # Handle files without extension + if [[ $filename == "$extension" ]]; then + extension="${ext:-jpg}" + fi + + echo "${dirname}/${filename}${suffix}.${extension}" +} diff --git a/scripts/meta/shell_check.sh b/scripts/meta/shell_check.sh index 9d462c5..34007ca 100755 --- a/scripts/meta/shell_check.sh +++ b/scripts/meta/shell_check.sh @@ -17,6 +17,11 @@ set -uo pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) + +# Source common library for log_info, log_warn, log_error +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + DEFAULT_ROOT=$(cd -- "$SCRIPT_DIR/../../" && pwd) ROOT_DIR="$DEFAULT_ROOT" @@ -25,20 +30,8 @@ INSTALL_ONLY="false" LIST_ONLY="false" VERBOSE="false" -log_info() { - printf '\033[1;34m[INFO]\033[0m %s\n' "$*" -} - -log_warn() { - printf '\033[1;33m[WARN]\033[0m %s\n' "$*" -} - -log_error() { - printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2 -} - usage() { - cat << EOF + cat < /dev/null 2>&1; } +is_cmd() { command -v "$1" >/dev/null 2>&1; } is_arch() { is_cmd pacman; } have_aur_helper() { is_cmd yay || is_cmd paru; } install_if_missing() { - local pkg cmd - pkg="$1" - cmd="$2" - if is_cmd "$cmd"; then - [[ $VERBOSE == "true" ]] && log_info "Found $cmd" - return 0 - fi + local pkg cmd + pkg="$1" + cmd="$2" + if is_cmd "$cmd"; then + [[ $VERBOSE == "true" ]] && log_info "Found $cmd" + return 0 + fi - if [[ $SKIP_INSTALL == "true" ]]; then - log_warn "Skipping install of $pkg ($cmd not found)" - return 1 - fi + if [[ $SKIP_INSTALL == "true" ]]; then + log_warn "Skipping install of $pkg ($cmd not found)" + return 1 + fi - if is_arch; then - log_info "Installing $pkg via pacman..." - if ! sudo pacman -S --needed --noconfirm "$pkg"; then - log_warn "Failed to install $pkg via pacman." - return 1 - fi - return 0 - else - log_warn "Non-Arch system detected. Please install '$pkg' manually." - return 1 - fi + if is_arch; then + log_info "Installing $pkg via pacman..." + if ! sudo pacman -S --needed --noconfirm "$pkg"; then + log_warn "Failed to install $pkg via pacman." + return 1 + fi + return 0 + else + log_warn "Non-Arch system detected. Please install '$pkg' manually." + return 1 + fi } install_linters() { - local ok=0 + local ok=0 - # Core linters - install_if_missing shellcheck shellcheck || ok=1 - install_if_missing shfmt shfmt || ok=1 + # Core linters + install_if_missing shellcheck shellcheck || ok=1 + install_if_missing shfmt shfmt || ok=1 - # Optional linters (best-effort) - # checkbashisms may be in repos or AUR; try pacman first, then AUR helper - if ! is_cmd checkbashisms; then - if is_arch; then - if ! sudo pacman -S --needed --noconfirm checkbashisms 2> /dev/null; then - if have_aur_helper; then - log_info "Installing checkbashisms from AUR (requires yay/paru)..." - if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi - if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi - else - log_warn "checkbashisms not installed (no AUR helper)." - fi - fi - fi - fi + # Optional linters (best-effort) + # checkbashisms may be in repos or AUR; try pacman first, then AUR helper + if ! is_cmd checkbashisms; then + if is_arch; then + if ! sudo pacman -S --needed --noconfirm checkbashisms 2>/dev/null; then + if have_aur_helper; then + log_info "Installing checkbashisms from AUR (requires yay/paru)..." + if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi + if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi + else + log_warn "checkbashisms not installed (no AUR helper)." + fi + fi + fi + fi - # bashate (python-based), typically available as python-bashate in AUR - if ! is_cmd bashate; then - if is_arch && have_aur_helper; then - log_info "Installing bashate from AUR (requires yay/paru)..." - if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi - if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi - else - # Try pip if user has it and wants to - if is_cmd pipx; then - log_info "Installing bashate via pipx..." - pipx install bashate || true - elif is_cmd pip3; then - log_info "Installing bashate via pip (user)..." - pip3 install --user bashate || true - else - log_warn "bashate not installed (no AUR helper or pip available)." - fi - fi - fi + # bashate (python-based), typically available as python-bashate in AUR + if ! is_cmd bashate; then + if is_arch && have_aur_helper; then + log_info "Installing bashate from AUR (requires yay/paru)..." + if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi + if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi + else + # Try pip if user has it and wants to + if is_cmd pipx; then + log_info "Installing bashate via pipx..." + pipx install bashate || true + elif is_cmd pip3; then + log_info "Installing bashate via pip (user)..." + pip3 install --user bashate || true + else + log_warn "bashate not installed (no AUR helper or pip available)." + fi + fi + fi - return "$ok" + return "$ok" } TMPDIR=$(mktemp -d) @@ -178,255 +173,255 @@ ABS_FILES_Z="$TMPDIR/files_abs.zlist" REL_FILES_Z="$TMPDIR/files_rel.zlist" discover_shell_files() { - local base="$1" - local -a all - all=() + local base="$1" + local -a all + all=() - if git -C "$base" rev-parse --is-inside-work-tree > /dev/null 2>&1; then - while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files -z) - while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z) - else - while IFS= read -r -d '' f; do - # trim leading ./ to keep consistent style with git paths - f="${f#./}" - f="${f#"${base}"/}" - all+=("$f") - done < <(find "$base" -type f -print0) - fi + if git -C "$base" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files -z) + while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z) + else + while IFS= read -r -d '' f; do + # trim leading ./ to keep consistent style with git paths + f="${f#./}" + f="${f#"${base}"/}" + all+=("$f") + done < <(find "$base" -type f -print0) + fi - local -a shells - shells=() + local -a shells + shells=() - for rel in "${all[@]}"; do - # skip binary-ish or huge files quickly by extension heuristic - case "$rel" in - *.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin) - continue - ;; - esac + for rel in "${all[@]}"; do + # skip binary-ish or huge files quickly by extension heuristic + case "$rel" in + *.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin) + continue + ;; + esac - local abs="$base/$rel" - [[ -f $abs && -r $abs ]] || continue + local abs="$base/$rel" + [[ -f $abs && -r $abs ]] || continue - if [[ $rel == *.sh || $rel == *.bash || $rel == *.zsh ]]; then - shells+=("$rel") - continue - fi + if [[ $rel == *.sh || $rel == *.bash || $rel == *.zsh ]]; then + shells+=("$rel") + continue + fi - # Check shebang - local first - first=$(head -n 1 -- "$abs" 2> /dev/null || true) - if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then - shells+=("$rel") - continue - fi + # Check shebang + local first + first=$(head -n 1 -- "$abs" 2>/dev/null || true) + if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then + shells+=("$rel") + continue + fi - # Also catch executable files with shell shebang even without extension - if [[ -x $abs ]]; then - if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then - shells+=("$rel") - fi - fi - done + # Also catch executable files with shell shebang even without extension + if [[ -x $abs ]]; then + if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then + shells+=("$rel") + fi + fi + done - # write lists - : > "$REL_FILES_Z" - : > "$ABS_FILES_Z" - for rel in "${shells[@]}"; do - printf '%s\0' "$rel" >> "$REL_FILES_Z" - printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z" - done + # write lists + : >"$REL_FILES_Z" + : >"$ABS_FILES_Z" + for rel in "${shells[@]}"; do + printf '%s\0' "$rel" >>"$REL_FILES_Z" + printf '%s\0' "$base/$rel" >>"$ABS_FILES_Z" + done } print_file_list() { - local count - count=$(tr -cd '\0' < "$REL_FILES_Z" | wc -c) - log_info "Discovered $count shell file(s) under $ROOT_DIR" - if [[ $VERBOSE == "true" ]]; then - tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /' - fi + local count + count=$(tr -cd '\0' <"$REL_FILES_Z" | wc -c) + log_info "Discovered $count shell file(s) under $ROOT_DIR" + if [[ $VERBOSE == "true" ]]; then + tr '\0' '\n' <"$REL_FILES_Z" | sed 's/^/ - /' + fi } run_linters() { - local issues=0 - local count - count=$(tr -cd '\0' < "$ABS_FILES_Z" | wc -c) - if [[ $count -eq 0 ]]; then - log_warn "No shell files found to lint." - return 0 - fi + local issues=0 + local count + count=$(tr -cd '\0' <"$ABS_FILES_Z" | wc -c) + if [[ $count -eq 0 ]]; then + log_warn "No shell files found to lint." + return 0 + fi - mapfile -d '' -t FILES < "$ABS_FILES_Z" + mapfile -d '' -t FILES <"$ABS_FILES_Z" - log_info "Running shellcheck..." - local sc_out="$TMPDIR/shellcheck.txt" - if is_cmd shellcheck; then - if ! shellcheck -x -S style "${FILES[@]}" > "$sc_out" 2>&1; then - issues=$((issues + 1)) - fi - else - log_warn "shellcheck not found; skipping" - fi + log_info "Running shellcheck..." + local sc_out="$TMPDIR/shellcheck.txt" + if is_cmd shellcheck; then + if ! shellcheck -x -S style "${FILES[@]}" >"$sc_out" 2>&1; then + issues=$((issues + 1)) + fi + else + log_warn "shellcheck not found; skipping" + fi - log_info "Running shfmt (diff mode)..." - local shfmt_out="$TMPDIR/shfmt.diff" - if is_cmd shfmt; then - if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" > "$shfmt_out" 2>&1; then - # shfmt returns non-zero when diff exists - issues=$((issues + 1)) - fi - else - log_warn "shfmt not found; skipping" - fi + log_info "Running shfmt (diff mode)..." + local shfmt_out="$TMPDIR/shfmt.diff" + if is_cmd shfmt; then + if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" >"$shfmt_out" 2>&1; then + # shfmt returns non-zero when diff exists + issues=$((issues + 1)) + fi + else + log_warn "shfmt not found; skipping" + fi - log_info "Running checkbashisms (optional)..." - local cbi_out="$TMPDIR/checkbashisms.txt" - local cbi_status=0 - if is_cmd checkbashisms; then - # Only run checkbashisms on scripts that are intended for /bin/sh (or unspecified), - # skip explicit bash/zsh scripts to avoid false positives. - local -a CBI_FILES - CBI_FILES=() - for f in "${FILES[@]}"; do - local first - first=$(head -n 1 -- "$f" 2> /dev/null || true) - if [[ $first =~ bash || $first =~ zsh ]]; then - continue - fi - CBI_FILES+=("$f") - done - if [[ ${#CBI_FILES[@]} -gt 0 ]]; then - # checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings - checkbashisms "${CBI_FILES[@]}" > "$cbi_out" 2>&1 - else - : > "$cbi_out" - fi - cbi_status=$? - if [[ $cbi_status -eq 1 ]]; then - issues=$((issues + 1)) - elif [[ $cbi_status -ne 0 ]]; then - log_warn "checkbashisms exited with status $cbi_status (treated as warning)" - fi - else - log_warn "checkbashisms not found; skipping" - fi + log_info "Running checkbashisms (optional)..." + local cbi_out="$TMPDIR/checkbashisms.txt" + local cbi_status=0 + if is_cmd checkbashisms; then + # Only run checkbashisms on scripts that are intended for /bin/sh (or unspecified), + # skip explicit bash/zsh scripts to avoid false positives. + local -a CBI_FILES + CBI_FILES=() + for f in "${FILES[@]}"; do + local first + first=$(head -n 1 -- "$f" 2>/dev/null || true) + if [[ $first =~ bash || $first =~ zsh ]]; then + continue + fi + CBI_FILES+=("$f") + done + if [[ ${#CBI_FILES[@]} -gt 0 ]]; then + # checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings + checkbashisms "${CBI_FILES[@]}" >"$cbi_out" 2>&1 + else + : >"$cbi_out" + fi + cbi_status=$? + if [[ $cbi_status -eq 1 ]]; then + issues=$((issues + 1)) + elif [[ $cbi_status -ne 0 ]]; then + log_warn "checkbashisms exited with status $cbi_status (treated as warning)" + fi + else + log_warn "checkbashisms not found; skipping" + fi - log_info "Running bash/zsh/sh syntax checks (-n)..." - local bash_out="$TMPDIR/bash_syntax.txt" - local zsh_out="$TMPDIR/zsh_syntax.txt" - local sh_out="$TMPDIR/sh_syntax.txt" + log_info "Running bash/zsh/sh syntax checks (-n)..." + local bash_out="$TMPDIR/bash_syntax.txt" + local zsh_out="$TMPDIR/zsh_syntax.txt" + local sh_out="$TMPDIR/sh_syntax.txt" - # Partition files by shebang for better accuracy - local -a BASH_FILES ZSH_FILES SH_FILES - BASH_FILES=() - ZSH_FILES=() - SH_FILES=() - for f in "${FILES[@]}"; do - local first - first=$(head -n 1 -- "$f" 2> /dev/null || true) - if [[ $first =~ bash ]]; then - BASH_FILES+=("$f") - elif [[ $first =~ zsh ]]; then - ZSH_FILES+=("$f") - else - SH_FILES+=("$f") - fi - done + # Partition files by shebang for better accuracy + local -a BASH_FILES ZSH_FILES SH_FILES + BASH_FILES=() + ZSH_FILES=() + SH_FILES=() + for f in "${FILES[@]}"; do + local first + first=$(head -n 1 -- "$f" 2>/dev/null || true) + if [[ $first =~ bash ]]; then + BASH_FILES+=("$f") + elif [[ $first =~ zsh ]]; then + ZSH_FILES+=("$f") + else + SH_FILES+=("$f") + fi + done - if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then - if ! bash -n "${BASH_FILES[@]}" 2> "$bash_out"; then - issues=$((issues + 1)) - fi - fi - if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then - if ! zsh -n "${ZSH_FILES[@]}" 2> "$zsh_out"; then - issues=$((issues + 1)) - fi - fi - # prefer dash if present for /bin/sh style - if [[ ${#SH_FILES[@]} -gt 0 ]]; then - if is_cmd dash; then - if ! dash -n "${SH_FILES[@]}" 2> "$sh_out"; then - issues=$((issues + 1)) - fi - elif is_cmd sh; then - if ! sh -n "${SH_FILES[@]}" 2> "$sh_out"; then - issues=$((issues + 1)) - fi - fi - fi + if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then + if ! bash -n "${BASH_FILES[@]}" 2>"$bash_out"; then + issues=$((issues + 1)) + fi + fi + if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then + if ! zsh -n "${ZSH_FILES[@]}" 2>"$zsh_out"; then + issues=$((issues + 1)) + fi + fi + # prefer dash if present for /bin/sh style + if [[ ${#SH_FILES[@]} -gt 0 ]]; then + if is_cmd dash; then + if ! dash -n "${SH_FILES[@]}" 2>"$sh_out"; then + issues=$((issues + 1)) + fi + elif is_cmd sh; then + if ! sh -n "${SH_FILES[@]}" 2>"$sh_out"; then + issues=$((issues + 1)) + fi + fi + fi - echo - log_info "========== Shell Lint Report ==========" + echo + log_info "========== Shell Lint Report ==========" - if [[ -s $sc_out ]]; then - printf '\n\033[1m-- shellcheck --\033[0m\n' - cat "$sc_out" - else - printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n' - fi + if [[ -s $sc_out ]]; then + printf '\n\033[1m-- shellcheck --\033[0m\n' + cat "$sc_out" + else + printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n' + fi - if [[ -s $shfmt_out ]]; then - printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n' - cat "$shfmt_out" - else - printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n' - fi + if [[ -s $shfmt_out ]]; then + printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n' + cat "$shfmt_out" + else + printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n' + fi - if [[ -s $cbi_out ]]; then - printf '\n\033[1m-- checkbashisms --\033[0m\n' - cat "$cbi_out" - else - printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n' - fi + if [[ -s $cbi_out ]]; then + printf '\n\033[1m-- checkbashisms --\033[0m\n' + cat "$cbi_out" + else + printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n' + fi - if [[ -s $bash_out ]]; then - printf '\n\033[1m-- bash -n (syntax) --\033[0m\n' - cat "$bash_out" - else - printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $bash_out ]]; then + printf '\n\033[1m-- bash -n (syntax) --\033[0m\n' + cat "$bash_out" + else + printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n' + fi - if [[ -s $zsh_out ]]; then - printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n' - cat "$zsh_out" - else - printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $zsh_out ]]; then + printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n' + cat "$zsh_out" + else + printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n' + fi - if [[ -s $sh_out ]]; then - printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n' - cat "$sh_out" - else - printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n' - fi + if [[ -s $sh_out ]]; then + printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n' + cat "$sh_out" + else + printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n' + fi - echo - if [[ $issues -gt 0 ]]; then - log_error "Linting completed with $issues tool(s) reporting issues." - return 1 - else - log_info "All checks passed." - return 0 - fi + echo + if [[ $issues -gt 0 ]]; then + log_error "Linting completed with $issues tool(s) reporting issues." + return 1 + else + log_info "All checks passed." + return 0 + fi } # Main if [[ $INSTALL_ONLY == "true" ]]; then - install_linters - exit $? + install_linters + exit $? fi # Only attempt installs if not list-only if [[ $LIST_ONLY != "true" ]]; then - install_linters || true + install_linters || true fi discover_shell_files "$ROOT_DIR" print_file_list if [[ $LIST_ONLY == "true" ]]; then - exit 0 + exit 0 fi run_linters diff --git a/scripts/misc/testsAndMisc-bash/fix_thorium_unity.sh b/scripts/misc/testsAndMisc-bash/fix_thorium_unity.sh index 5c50bc9..6849d70 100644 --- a/scripts/misc/testsAndMisc-bash/fix_thorium_unity.sh +++ b/scripts/misc/testsAndMisc-bash/fix_thorium_unity.sh @@ -18,22 +18,17 @@ set -euo pipefail IFS=$'\n\t' -GREEN="\033[1;32m" -YELLOW="\033[1;33m" -RED="\033[1;31m" -BLUE="\033[1;34m" -NC="\033[0m" -log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } -log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -log_error() { echo -e "${RED}[ERR ]${NC} $*" 1>&2; } +# Source common library for log functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../../lib/common.sh +source "$SCRIPT_DIR/../../lib/common.sh" DO_POLICY=false SET_DEFAULT=false DO_RESTART=false usage() { - cat << EOF + cat < /dev/null 2>&1; then - log_error "sudo not found; cannot install system policy. Use --set-default or run from root." - exit 1 - fi + if ! command -v sudo >/dev/null 2>&1; then + log_error "sudo not found; cannot install system policy. Use --set-default or run from root." + exit 1 + fi } install_policy() { - ensure_sudo - # Candidate policy directories (most common for Chromium forks) - local candidates=( - "/etc/thorium-browser/policies/managed" # Thorium - "/etc/chromium/policies/managed" # Chromium - "/etc/opt/chrome/policies/managed" # Google Chrome - ) - local wrote_any=false - for target in "${candidates[@]}"; do - log_info "Installing policy into: $target" - sudo mkdir -p "$target" - local policy_file="$target/unityhub-policy.json" - sudo tee "$policy_file" > /dev/null << 'JSON' + ensure_sudo + # Candidate policy directories (most common for Chromium forks) + local candidates=( + "/etc/thorium-browser/policies/managed" # Thorium + "/etc/chromium/policies/managed" # Chromium + "/etc/opt/chrome/policies/managed" # Google Chrome + ) + local wrote_any=false + for target in "${candidates[@]}"; do + log_info "Installing policy into: $target" + sudo mkdir -p "$target" + local policy_file="$target/unityhub-policy.json" + sudo tee "$policy_file" >/dev/null <<'JSON' { "AutoLaunchProtocolsFromOrigins": [ { "protocol": "unityhub", "origin": "https://id.unity.com", "allow": true }, @@ -106,53 +101,53 @@ install_policy() { ] } JSON - # Some Chromium builds cache policies; no explicit reload on Linux. Restarting browser suffices. - log_ok "Policy written: $policy_file" - wrote_any=true - done - if [[ $wrote_any != true ]]; then - log_warn "Policy may not have been written. No candidate directories processed." - fi + # Some Chromium builds cache policies; no explicit reload on Linux. Restarting browser suffices. + log_ok "Policy written: $policy_file" + wrote_any=true + done + if [[ $wrote_any != true ]]; then + log_warn "Policy may not have been written. No candidate directories processed." + fi } set_default_browser() { - if command -v xdg-settings > /dev/null 2>&1; then - # Prefer the upstream desktop id if it exists - local desktop="thorium-browser.desktop" - if [[ ! -f "/usr/share/applications/$desktop" && -f "$HOME/.local/share/applications/$desktop" ]]; then - : # keep desktop as is - elif [[ ! -f "/usr/share/applications/$desktop" && ! -f "$HOME/.local/share/applications/$desktop" ]]; then - log_warn "thorium-browser.desktop not found; leaving default browser unchanged." - return - fi - log_info "Setting default browser to $desktop" - xdg-settings set default-web-browser "$desktop" || log_warn "Failed to set default browser via xdg-settings" - log_ok "Default browser set to: $(xdg-settings get default-web-browser 2> /dev/null || echo "$desktop")" - else - log_warn "xdg-settings not found; cannot set default browser automatically." - fi + if command -v xdg-settings >/dev/null 2>&1; then + # Prefer the upstream desktop id if it exists + local desktop="thorium-browser.desktop" + if [[ ! -f "/usr/share/applications/$desktop" && -f "$HOME/.local/share/applications/$desktop" ]]; then + : # keep desktop as is + elif [[ ! -f "/usr/share/applications/$desktop" && ! -f "$HOME/.local/share/applications/$desktop" ]]; then + log_warn "thorium-browser.desktop not found; leaving default browser unchanged." + return + fi + log_info "Setting default browser to $desktop" + xdg-settings set default-web-browser "$desktop" || log_warn "Failed to set default browser via xdg-settings" + log_ok "Default browser set to: $(xdg-settings get default-web-browser 2>/dev/null || echo "$desktop")" + else + log_warn "xdg-settings not found; cannot set default browser automatically." + fi } restart_thorium() { - # Kill Thorium processes and start fresh - log_info "Restarting Thorium..." - pkill -9 -f 'thorium-browser' 2> /dev/null || true - # Also kill unityhub-bin's embedded Chromium if any leftover (harmless) - pkill -9 -f 'unityhub-bin' 2> /dev/null || true - # Start Thorium detached if available - if command -v thorium-browser > /dev/null 2>&1; then - nohup thorium-browser > /dev/null 2>&1 & - disown || true - fi - log_ok "Thorium restart attempted." + # Kill Thorium processes and start fresh + log_info "Restarting Thorium..." + pkill -9 -f 'thorium-browser' 2>/dev/null || true + # Also kill unityhub-bin's embedded Chromium if any leftover (harmless) + pkill -9 -f 'unityhub-bin' 2>/dev/null || true + # Start Thorium detached if available + if command -v thorium-browser >/dev/null 2>&1; then + nohup thorium-browser >/dev/null 2>&1 & + disown || true + fi + log_ok "Thorium restart attempted." } main() { - $DO_POLICY && install_policy - $SET_DEFAULT && set_default_browser - $DO_RESTART && restart_thorium + $DO_POLICY && install_policy + $SET_DEFAULT && set_default_browser + $DO_RESTART && restart_thorium - cat << 'NEXT' + cat <<'NEXT' --- Next steps: - Open Unity Hub, click Sign in, complete in Thorium; when prompted, allow the unityhub link to open the app. diff --git a/scripts/misc/testsAndMisc-bash/fix_unity.sh b/scripts/misc/testsAndMisc-bash/fix_unity.sh index 3165ab7..1d9b5b8 100755 --- a/scripts/misc/testsAndMisc-bash/fix_unity.sh +++ b/scripts/misc/testsAndMisc-bash/fix_unity.sh @@ -23,19 +23,14 @@ set -euo pipefail IFS=$'\n\t' SCRIPT_NAME="$(basename "$0")" -GREEN="\033[1;32m" -YELLOW="\033[1;33m" -RED="\033[1;31m" -BLUE="\033[1;34m" -NC="\033[0m" -log_info() { echo -e "${BLUE}[INFO]${NC} $*"; } -log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -log_error() { echo -e "${RED}[ERR ]${NC} $*" 1>&2; } +# Source common library for log functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../../lib/common.sh +source "$SCRIPT_DIR/../../lib/common.sh" usage() { - cat << EOF + cat < /dev/null 2>&1; then - return 1 - fi + if ! command -v "$1" >/dev/null 2>&1; then + return 1 + fi } ensure_deps_arch() { - # Best-effort install for Arch-based systems - if [[ $AUTO_INSTALL != true ]]; then - log_warn "Skipping package installation (use -y to auto-install)." - return 0 - fi - if ! require_cmd pacman; then - log_warn "Not an Arch-based system (pacman not found). Skipping auto-install." - return 0 - fi - local pkgs=(xdg-utils desktop-file-utils xdg-desktop-portal xdg-desktop-portal-gtk) - log_info "Installing/ensuring packages: ${pkgs[*]}" - if ! require_cmd sudo; then - log_warn "sudo not found; attempting pacman directly (may fail)." - sudo_cmd="" - else - sudo_cmd="sudo" - fi - # Use --needed to avoid reinstalling - set +e - $sudo_cmd pacman -S --needed --noconfirm "${pkgs[@]}" - local rc=$? - set -e - if [[ $rc -ne 0 ]]; then - log_warn "Package install may have failed or been partial. Continuing anyway." - else - log_ok "Dependencies installed/verified." - fi + # Best-effort install for Arch-based systems + if [[ $AUTO_INSTALL != true ]]; then + log_warn "Skipping package installation (use -y to auto-install)." + return 0 + fi + if ! require_cmd pacman; then + log_warn "Not an Arch-based system (pacman not found). Skipping auto-install." + return 0 + fi + local pkgs=(xdg-utils desktop-file-utils xdg-desktop-portal xdg-desktop-portal-gtk) + log_info "Installing/ensuring packages: ${pkgs[*]}" + if ! require_cmd sudo; then + log_warn "sudo not found; attempting pacman directly (may fail)." + sudo_cmd="" + else + sudo_cmd="sudo" + fi + # Use --needed to avoid reinstalling + set +e + $sudo_cmd pacman -S --needed --noconfirm "${pkgs[@]}" + local rc=$? + set -e + if [[ $rc -ne 0 ]]; then + log_warn "Package install may have failed or been partial. Continuing anyway." + else + log_ok "Dependencies installed/verified." + fi } desktop_dir="$HOME/.local/share/applications" mkdir -p "$desktop_dir" detect_unityhub() { - # Outputs: INSTALL_TYPE (FLATPAK|NATIVE|APPIMAGE|UNKNOWN) and EXEC_CMD - local install_type="UNKNOWN" exec_cmd="" + # Outputs: INSTALL_TYPE (FLATPAK|NATIVE|APPIMAGE|UNKNOWN) and EXEC_CMD + local install_type="UNKNOWN" exec_cmd="" - # 1) Flatpak - if command -v flatpak > /dev/null 2>&1; then - if flatpak info com.unity.UnityHub > /dev/null 2>&1; then - install_type="FLATPAK" - exec_cmd="flatpak run com.unity.UnityHub %U" - echo "$install_type|$exec_cmd" - return 0 - fi - fi + # 1) Flatpak + if command -v flatpak >/dev/null 2>&1; then + if flatpak info com.unity.UnityHub >/dev/null 2>&1; then + install_type="FLATPAK" + exec_cmd="flatpak run com.unity.UnityHub %U" + echo "$install_type|$exec_cmd" + return 0 + fi + fi - # 2) Native binary in PATH - if command -v unityhub > /dev/null 2>&1; then - local path - path="$(command -v unityhub)" - install_type="NATIVE" - exec_cmd="$path %U" - echo "$install_type|$exec_cmd" - return 0 - fi + # 2) Native binary in PATH + if command -v unityhub >/dev/null 2>&1; then + local path + path="$(command -v unityhub)" + install_type="NATIVE" + exec_cmd="$path %U" + echo "$install_type|$exec_cmd" + return 0 + fi - # 3) Search desktop files for Unity Hub Exec - local search_dirs=( - "$HOME/.local/share/applications" - "/usr/share/applications" - "/var/lib/flatpak/exports/share/applications" - "$HOME/.local/share/flatpak/exports/share/applications" - ) - local found_exec="" - for d in "${search_dirs[@]}"; do - [[ -d $d ]] || continue - # prefer official naming when present - local f - for f in "$d"/*.desktop; do - [[ -e $f ]] || continue - if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2> /dev/null || - grep -qiE 'Exec=.*unityhub' "$f" 2> /dev/null; then - local exec_line - exec_line="$(grep -iE '^Exec=' "$f" | head -n1 | sed 's/^Exec=//')" - if [[ -n $exec_line ]]; then - found_exec="$exec_line" - break 2 - fi - fi - done - done + # 3) Search desktop files for Unity Hub Exec + local search_dirs=( + "$HOME/.local/share/applications" + "/usr/share/applications" + "/var/lib/flatpak/exports/share/applications" + "$HOME/.local/share/flatpak/exports/share/applications" + ) + local found_exec="" + for d in "${search_dirs[@]}"; do + [[ -d $d ]] || continue + # prefer official naming when present + local f + for f in "$d"/*.desktop; do + [[ -e $f ]] || continue + if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2>/dev/null || + grep -qiE 'Exec=.*unityhub' "$f" 2>/dev/null; then + local exec_line + exec_line="$(grep -iE '^Exec=' "$f" | head -n1 | sed 's/^Exec=//')" + if [[ -n $exec_line ]]; then + found_exec="$exec_line" + break 2 + fi + fi + done + done - if [[ -n $found_exec ]]; then - # Normalize: ensure %U present - if [[ $found_exec != *"%U"* && $found_exec != *"%u"* ]]; then - found_exec+=" %U" - fi - if [[ $found_exec == flatpak* ]]; then - install_type="FLATPAK" - elif [[ $found_exec == *AppImage* || $found_exec == *appimage* ]]; then - install_type="APPIMAGE" - else - install_type="NATIVE" - fi - echo "$install_type|$found_exec" - return 0 - fi + if [[ -n $found_exec ]]; then + # Normalize: ensure %U present + if [[ $found_exec != *"%U"* && $found_exec != *"%u"* ]]; then + found_exec+=" %U" + fi + if [[ $found_exec == flatpak* ]]; then + install_type="FLATPAK" + elif [[ $found_exec == *AppImage* || $found_exec == *appimage* ]]; then + install_type="APPIMAGE" + else + install_type="NATIVE" + fi + echo "$install_type|$found_exec" + return 0 + fi - # 4) Try common AppImage locations - local ai_candidates=( - "$HOME/Applications/UnityHub*.AppImage" - "$HOME/.local/bin/UnityHub*.AppImage" - "/opt/UnityHub*/UnityHub*.AppImage" - ) - local ai - for ai in "${ai_candidates[@]}"; do - for p in $ai; do - if [[ -f $p && -x $p ]]; then - install_type="APPIMAGE" - exec_cmd="$p %U" - echo "$install_type|$exec_cmd" - return 0 - fi - done - done + # 4) Try common AppImage locations + local ai_candidates=( + "$HOME/Applications/UnityHub*.AppImage" + "$HOME/.local/bin/UnityHub*.AppImage" + "/opt/UnityHub*/UnityHub*.AppImage" + ) + local ai + for ai in "${ai_candidates[@]}"; do + for p in $ai; do + if [[ -f $p && -x $p ]]; then + install_type="APPIMAGE" + exec_cmd="$p %U" + echo "$install_type|$exec_cmd" + return 0 + fi + done + done - echo "$install_type|$exec_cmd" + echo "$install_type|$exec_cmd" } create_handler_desktop() { - local exec_cmd="$1" - local dest="$desktop_dir/unityhub-url-handler.desktop" - log_info "Writing handler desktop entry: $dest" - cat > "$dest" << DESK + local exec_cmd="$1" + local dest="$desktop_dir/unityhub-url-handler.desktop" + log_info "Writing handler desktop entry: $dest" + cat >"$dest" < /dev/null 2>&1; then - update-desktop-database "$desktop_dir" || true - else - log_warn "update-desktop-database not found (install desktop-file-utils)." - fi + local desktop_file="$1" + # Update desktop database if available + if command -v update-desktop-database >/dev/null 2>&1; then + update-desktop-database "$desktop_dir" || true + else + log_warn "update-desktop-database not found (install desktop-file-utils)." + fi - # Register as default handler for both schemes - if command -v xdg-mime > /dev/null 2>&1; then - xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unityhub || true - xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unity || true - else - log_error "xdg-mime not found (install xdg-utils)." - return 1 - fi - log_ok "MIME handler registered for unityhub:// (and unity://)." + # Register as default handler for both schemes + if command -v xdg-mime >/dev/null 2>&1; then + xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unityhub || true + xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unity || true + else + log_error "xdg-mime not found (install xdg-utils)." + return 1 + fi + log_ok "MIME handler registered for unityhub:// (and unity://)." } verify_registration() { - local expected cur1 cur2 - expected="$(basename "$1")" - cur1="$(xdg-mime query default x-scheme-handler/unityhub 2> /dev/null || true)" - cur2="$(xdg-mime query default x-scheme-handler/unity 2> /dev/null || true)" - log_info "Current handler (unityhub): ${cur1:-}" - log_info "Current handler (unity): ${cur2:-}" - if [[ $cur1 == "$expected" ]]; then - log_ok "unityhub scheme correctly set to $expected" - else - log_warn "unityhub scheme not set to $expected (currently: ${cur1:-none})." - fi + local expected cur1 cur2 + expected="$(basename "$1")" + cur1="$(xdg-mime query default x-scheme-handler/unityhub 2>/dev/null || true)" + cur2="$(xdg-mime query default x-scheme-handler/unity 2>/dev/null || true)" + log_info "Current handler (unityhub): ${cur1:-}" + log_info "Current handler (unity): ${cur2:-}" + if [[ $cur1 == "$expected" ]]; then + log_ok "unityhub scheme correctly set to $expected" + else + log_warn "unityhub scheme not set to $expected (currently: ${cur1:-none})." + fi } maybe_test_open() { - if [[ $RUN_TEST == true ]]; then - log_info "Opening test link: unityhub://v1/editor-signin" - if command -v xdg-open > /dev/null 2>&1; then - xdg-open 'unityhub://v1/editor-signin' > /dev/null 2>&1 || true - log_ok "Test link invoked. Check if Unity Hub launches or focuses." - else - log_warn "xdg-open not found; cannot run test automatically." - fi - else - log_info "You can test manually with: xdg-open 'unityhub://v1/editor-signin'" - fi + if [[ $RUN_TEST == true ]]; then + log_info "Opening test link: unityhub://v1/editor-signin" + if command -v xdg-open >/dev/null 2>&1; then + xdg-open 'unityhub://v1/editor-signin' >/dev/null 2>&1 || true + log_ok "Test link invoked. Check if Unity Hub launches or focuses." + else + log_warn "xdg-open not found; cannot run test automatically." + fi + else + log_info "You can test manually with: xdg-open 'unityhub://v1/editor-signin'" + fi } main() { - log_info "Ensuring required tools (optional)." - ensure_deps_arch + log_info "Ensuring required tools (optional)." + ensure_deps_arch - log_info "Detecting Unity Hub installation..." - IFS='|' read -r install_type exec_cmd < <(detect_unityhub) - log_info "Detected type: $install_type" - if [[ -z ${exec_cmd:-} ]]; then - log_warn "Could not find Unity Hub executable automatically." - log_warn "- If using Flatpak: install with 'flatpak install flathub com.unity.UnityHub'" - log_warn "- If native (AUR): ensure 'unityhub' is in PATH" - log_warn "- If AppImage: place it in ~/Applications and make it executable" - log_error "Aborting—no Exec command available to create handler." - exit 2 - fi - log_info "Using Exec: $exec_cmd" + log_info "Detecting Unity Hub installation..." + IFS='|' read -r install_type exec_cmd < <(detect_unityhub) + log_info "Detected type: $install_type" + if [[ -z ${exec_cmd:-} ]]; then + log_warn "Could not find Unity Hub executable automatically." + log_warn "- If using Flatpak: install with 'flatpak install flathub com.unity.UnityHub'" + log_warn "- If native (AUR): ensure 'unityhub' is in PATH" + log_warn "- If AppImage: place it in ~/Applications and make it executable" + log_error "Aborting—no Exec command available to create handler." + exit 2 + fi + log_info "Using Exec: $exec_cmd" - local desktop_file - desktop_file="$(create_handler_desktop "$exec_cmd")" + local desktop_file + desktop_file="$(create_handler_desktop "$exec_cmd")" - register_mime_handler "$desktop_file" - verify_registration "$desktop_file" + register_mime_handler "$desktop_file" + verify_registration "$desktop_file" - cat << 'NOTE' + cat <<'NOTE' --- Next steps: - Sign in from Unity Hub. When the browser finishes, ALLOW the prompt to open xdg-open/Unity Hub. @@ -299,9 +294,9 @@ Next steps: --- NOTE - maybe_test_open + maybe_test_open - log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R \"x-scheme-handler/unityhub\" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10" + log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R \"x-scheme-handler/unityhub\" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10" } main "$@" diff --git a/scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh b/scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh index 9b1d856..4fda3b2 100755 --- a/scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh +++ b/scripts/misc/testsAndMisc-bash/get_rnnoise_model.sh @@ -11,28 +11,23 @@ set -euo pipefail # Bash/get_rnnoise_model.sh # interactive download # RN_TARGET_DIR=./models Bash/get_rnnoise_model.sh --yes -ask_yes_no() { - read -r -p "$1 [y/N]: " ans || true - case "${ans:-}" in - y | Y | yes | YES) return 0 ;; - *) return 1 ;; - esac -} - -has_cmd() { command -v "$1" > /dev/null 2>&1; } +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../../lib/common.sh +source "$SCRIPT_DIR/../../lib/common.sh" YES=false while [[ $# -gt 0 ]]; do - case "$1" in - -y | --yes) - YES=true - shift - ;; - *) - echo "Unknown option: $1" >&2 - exit 2 - ;; - esac + case "$1" in + -y | --yes) + YES=true + shift + ;; + *) + echo "Unknown option: $1" >&2 + exit 2 + ;; + esac done RN_TARGET_DIR=${RN_TARGET_DIR:-"$(dirname "$0")/models"} @@ -42,153 +37,125 @@ mkdir -p "$RN_TARGET_DIR" dest="$RN_TARGET_DIR/$RN_TARGET_NAME" if [[ -f $dest ]]; then - echo "Model already exists at: $dest" - exit 0 + echo "Model already exists at: $dest" + exit 0 fi if ! $YES; then - if ! ask_yes_no "Download RNNoise model to $dest?"; then - echo "Aborted." - exit 1 - fi + if ! ask_yes_no "Download RNNoise model to $dest?"; then + echo "Aborted." + exit 1 + fi fi if ! has_cmd curl && ! has_cmd wget; then - echo "Error: Need curl or wget to download RNNoise model." >&2 - exit 3 + echo "Error: Need curl or wget to download RNNoise model." >&2 + exit 3 fi +# Helper: try to download a URL to destination, exit 0 on success +# Usage: try_download_model URL DEST +try_download_model() { + local url="$1" + local dest="$2" + local tmp + tmp=$(mktemp) + echo "Attempting to download RNNoise model from: $url" >&2 + if has_cmd curl; then + curl -fsSL "$url" -o "$tmp" 2>/dev/null || true + else + wget -qO "$tmp" "$url" 2>/dev/null || true + fi + if [[ -s $tmp ]]; then + mv "$tmp" "$dest" + echo "Saved RNNoise model to: $dest" >&2 + exit 0 + fi + rm -f "$tmp" || true +} + # Priority 1: explicit URL if [[ -n ${RN_URL:-} ]]; then - echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2 - tmp=$(mktemp) - if has_cmd curl; then - curl -fsSL "$RN_URL" -o "$tmp" - else - wget -qO "$tmp" "$RN_URL" - fi - if [[ -s $tmp ]]; then - mv "$tmp" "$dest" - echo "Saved RNNoise model to: $dest" - exit 0 - fi - rm -f "$tmp" || true - echo "Warning: RN_URL download failed; continuing to fallback sources." >&2 + echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2 + try_download_model "$RN_URL" "$dest" + echo "Warning: RN_URL download failed; continuing to fallback sources." >&2 fi # Priority 2: rnnoise-nu known models (GregorR) NU_URLS=( - "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/sh.rnnn" - "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/lq.rnnn" - "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/mp.rnnn" - "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/bd.rnnn" - "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn" + "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/sh.rnnn" + "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/lq.rnnn" + "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/mp.rnnn" + "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/bd.rnnn" + "https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn" ) for u in "${NU_URLS[@]}"; do - echo "Attempting to download RNNoise model from: $u" >&2 - tmp=$(mktemp) - if has_cmd curl; then - if curl -fsSL "$u" -o "$tmp"; then - if [[ -s $tmp ]]; then - mv "$tmp" "$dest" - echo "Saved RNNoise model to: $dest" >&2 - exit 0 - fi - fi - else - if wget -qO "$tmp" "$u"; then - if [[ -s $tmp ]]; then - mv "$tmp" "$dest" - echo "Saved RNNoise model to: $dest" >&2 - exit 0 - fi - fi - fi - rm -f "$tmp" || true + try_download_model "$u" "$dest" done # Priority 2b: arnndn-models fallback (richardpl) RNNDN_URLS=( - "https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn" + "https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn" ) for u in "${RNNDN_URLS[@]}"; do - echo "Attempting to download RNNoise model from: $u" >&2 - tmp=$(mktemp) - if has_cmd curl; then - if curl -fsSL "$u" -o "$tmp"; then - if [[ -s $tmp ]]; then - mv "$tmp" "$dest" - echo "Saved RNNoise model to: $dest" >&2 - exit 0 - fi - fi - else - if wget -qO "$tmp" "$u"; then - if [[ -s $tmp ]]; then - mv "$tmp" "$dest" - echo "Saved RNNoise model to: $dest" >&2 - exit 0 - fi - fi - fi - rm -f "$tmp" || true + try_download_model "$u" "$dest" done # Priority 3: repo archives (rnnoise-nu and arnndn-models) ARCHIVES=( - "https://github.com/GregorR/rnnoise-nu/archive/refs/heads/master.zip" - "https://github.com/richardpl/arnndn-models/archive/refs/heads/master.zip" + "https://github.com/GregorR/rnnoise-nu/archive/refs/heads/master.zip" + "https://github.com/richardpl/arnndn-models/archive/refs/heads/master.zip" ) for aurl in "${ARCHIVES[@]}"; do - echo "Attempting to download archive: $aurl" >&2 - tmpdir=$(mktemp -d) - archive="$tmpdir/models.zip" - set +e - if has_cmd curl; then - curl -fL "$aurl" -o "$archive" - else - wget -O "$archive" "$aurl" - fi - status=$? - set -e - if [[ $status -ne 0 ]]; then - rm -rf "$tmpdir" || true - continue - fi - if has_cmd bsdtar; then - bsdtar -xf "$archive" -C "$tmpdir" - elif has_cmd unzip; then - unzip -q "$archive" -d "$tmpdir" - else - echo "Warning: Need bsdtar or unzip to extract archive; skipping archive method." >&2 - rm -rf "$tmpdir" || true - continue - fi - mapfile -t nnfiles < <(bash -lc 'shopt -s globstar nullglob; for f in '"$tmpdir"'/**/*.rnnn '"$tmpdir"'/**/*.nn; do [[ -f "$f" ]] && echo "$f"; done') - if [[ ${#nnfiles[@]} -gt 0 ]]; then - cp -f "${nnfiles[0]}" "$dest" - echo "Saved RNNoise model to: $dest (from archive)" >&2 - rm -rf "$tmpdir" || true - exit 0 - fi - rm -rf "$tmpdir" || true + echo "Attempting to download archive: $aurl" >&2 + tmpdir=$(mktemp -d) + archive="$tmpdir/models.zip" + set +e + if has_cmd curl; then + curl -fL "$aurl" -o "$archive" + else + wget -O "$archive" "$aurl" + fi + status=$? + set -e + if [[ $status -ne 0 ]]; then + rm -rf "$tmpdir" || true + continue + fi + if has_cmd bsdtar; then + bsdtar -xf "$archive" -C "$tmpdir" + elif has_cmd unzip; then + unzip -q "$archive" -d "$tmpdir" + else + echo "Warning: Need bsdtar or unzip to extract archive; skipping archive method." >&2 + rm -rf "$tmpdir" || true + continue + fi + mapfile -t nnfiles < <(bash -lc 'shopt -s globstar nullglob; for f in '"$tmpdir"'/**/*.rnnn '"$tmpdir"'/**/*.nn; do [[ -f "$f" ]] && echo "$f"; done') + if [[ ${#nnfiles[@]} -gt 0 ]]; then + cp -f "${nnfiles[0]}" "$dest" + echo "Saved RNNoise model to: $dest (from archive)" >&2 + rm -rf "$tmpdir" || true + exit 0 + fi + rm -rf "$tmpdir" || true done # Priority 4: Arch-based AUR packages and search only .nn/.rnnn if has_cmd yay; then - echo "Attempting to install AUR packages that may include RNNoise models..." >&2 - set +e - yay -S --noconfirm denoiseit-git 2> /dev/null - yay -S --noconfirm speech-denoiser-git 2> /dev/null - set -e - mapfile -t found < <(bash -lc 'shopt -s globstar nullglob; for f in /usr/share/**/*.nn /usr/share/**/*.rnnn /usr/local/share/**/*.nn /usr/local/share/**/*.rnnn; do [[ -f "$f" ]] && echo "$f"; done' 2> /dev/null || true) - if [[ ${#found[@]} -gt 0 ]]; then - echo "Found candidate models:" >&2 - printf ' %s\n' "${found[@]}" >&2 - cp -f "${found[0]}" "$dest" - echo "Copied model to: $dest" >&2 - exit 0 - fi + echo "Attempting to install AUR packages that may include RNNoise models..." >&2 + set +e + yay -S --noconfirm denoiseit-git 2>/dev/null + yay -S --noconfirm speech-denoiser-git 2>/dev/null + set -e + mapfile -t found < <(bash -lc 'shopt -s globstar nullglob; for f in /usr/share/**/*.nn /usr/share/**/*.rnnn /usr/local/share/**/*.nn /usr/local/share/**/*.rnnn; do [[ -f "$f" ]] && echo "$f"; done' 2>/dev/null || true) + if [[ ${#found[@]} -gt 0 ]]; then + echo "Found candidate models:" >&2 + printf ' %s\n' "${found[@]}" >&2 + cp -f "${found[0]}" "$dest" + echo "Copied model to: $dest" >&2 + exit 0 + fi fi echo "Error: Could not obtain an RNNoise model automatically." >&2 diff --git a/scripts/misc/testsAndMisc-bash/install_ffmpeg_with_arnndn.sh b/scripts/misc/testsAndMisc-bash/install_ffmpeg_with_arnndn.sh index 12cd94b..c17278f 100755 --- a/scripts/misc/testsAndMisc-bash/install_ffmpeg_with_arnndn.sh +++ b/scripts/misc/testsAndMisc-bash/install_ffmpeg_with_arnndn.sh @@ -7,124 +7,119 @@ set -euo pipefail # Tries distro packages first; if not suitable, offers to build from source. # This script prints commands and asks for confirmation before building. +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../../lib/common.sh +source "$SCRIPT_DIR/../../lib/common.sh" + print_info() { - echo "[info] $*" + echo "[info] $*" } -ask_yes_no() { - read -r -p "$1 [y/N]: " ans || true - case "${ans:-}" in - y | Y | yes | YES) return 0 ;; - *) return 1 ;; - esac -} - -has_cmd() { command -v "$1" > /dev/null 2>&1; } - detect_distro() { - if [[ -f /etc/os-release ]]; then - . /etc/os-release - echo "${ID:-unknown}" - else - echo "unknown" - fi + if [[ -f /etc/os-release ]]; then + . /etc/os-release + echo "${ID:-unknown}" + else + echo "unknown" + fi } main() { - local distro - distro=$(detect_distro) - print_info "Detected distro: $distro" + local distro + distro=$(detect_distro) + print_info "Detected distro: $distro" - if has_cmd ffmpeg && ffmpeg -hide_banner -filters | grep -q " arnndn "; then - print_info "Your ffmpeg already supports arnndn." - else - case "$distro" in - ubuntu | debian) - print_info "On Ubuntu/Debian, the official repo may lack newer filters. Consider a PPA or build from source." - echo "Options:" - echo " - ppa: sudo add-apt-repository ppa:savoury1/ffmpeg6 && sudo apt update && sudo apt install ffmpeg" - echo " - source build (recommended for latest): run this script to build from source" - ;; - arch | manjaro | endeavouros) - print_info "On Arch-based distros, ffmpeg is recent. Try: sudo pacman -Syu ffmpeg" - ;; - fedora) - print_info "On Fedora, try: sudo dnf install ffmpeg" - ;; - *) - print_info "Distro not recognized; will offer source build." - ;; - esac - fi + if has_cmd ffmpeg && ffmpeg -hide_banner -filters | grep -q " arnndn "; then + print_info "Your ffmpeg already supports arnndn." + else + case "$distro" in + ubuntu | debian) + print_info "On Ubuntu/Debian, the official repo may lack newer filters. Consider a PPA or build from source." + echo "Options:" + echo " - ppa: sudo add-apt-repository ppa:savoury1/ffmpeg6 && sudo apt update && sudo apt install ffmpeg" + echo " - source build (recommended for latest): run this script to build from source" + ;; + arch | manjaro | endeavouros) + print_info "On Arch-based distros, ffmpeg is recent. Try: sudo pacman -Syu ffmpeg" + ;; + fedora) + print_info "On Fedora, try: sudo dnf install ffmpeg" + ;; + *) + print_info "Distro not recognized; will offer source build." + ;; + esac + fi - if ask_yes_no "Build FFmpeg from source with rnnoise/arnndn support now?"; then - echo "This will clone FFmpeg and build locally under ./ffmpeg-build. Continue?" - if ! ask_yes_no "Proceed"; then - exit 0 - fi - set -x - mkdir -p ffmpeg-build && cd ffmpeg-build - # Prepare repository - if [[ -d FFmpeg ]]; then - if [[ -d FFmpeg/.git ]]; then - if ask_yes_no "An existing FFmpeg source directory was found. Reuse and update it?"; then - set +e - git -C FFmpeg fetch --all --tags --prune - git -C FFmpeg pull --rebase --ff-only || true - set -e - else - if ask_yes_no "Delete existing FFmpeg directory and re-clone?"; then - rm -rf FFmpeg - else - echo "Keeping existing FFmpeg directory as-is." - fi - fi - else - if ask_yes_no "Non-git 'FFmpeg' directory exists. Delete and re-clone?"; then - rm -rf FFmpeg - else - echo "Cannot proceed with a non-git FFmpeg directory present. Aborting." - exit 4 - fi - fi - fi - # Dependencies - if [[ $distro == "ubuntu" || $distro == "debian" ]]; then - sudo apt update - sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev - elif [[ $distro == "arch" || $distro == "manjaro" || $distro == "endeavouros" ]]; then - sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise - elif [[ $distro == "fedora" ]]; then - sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel - else - echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2 - fi - if [[ ! -d FFmpeg/.git ]]; then - git clone https://github.com/FFmpeg/FFmpeg.git --depth=1 - fi - cd FFmpeg - RN_FLAG="" - # Some FFmpeg versions auto-detect rnnoise without a flag; include the flag only if supported - if ./configure --help | grep -q "librnnoise"; then - RN_FLAG="--enable-librnnoise" - else - echo "[info] configure has no --enable-librnnoise; relying on auto-detection via pkg-config (rnnoise)." >&2 - fi + if ask_yes_no "Build FFmpeg from source with rnnoise/arnndn support now?"; then + echo "This will clone FFmpeg and build locally under ./ffmpeg-build. Continue?" + if ! ask_yes_no "Proceed"; then + exit 0 + fi + set -x + mkdir -p ffmpeg-build && cd ffmpeg-build + # Prepare repository + if [[ -d FFmpeg ]]; then + if [[ -d FFmpeg/.git ]]; then + if ask_yes_no "An existing FFmpeg source directory was found. Reuse and update it?"; then + set +e + git -C FFmpeg fetch --all --tags --prune + git -C FFmpeg pull --rebase --ff-only || true + set -e + else + if ask_yes_no "Delete existing FFmpeg directory and re-clone?"; then + rm -rf FFmpeg + else + echo "Keeping existing FFmpeg directory as-is." + fi + fi + else + if ask_yes_no "Non-git 'FFmpeg' directory exists. Delete and re-clone?"; then + rm -rf FFmpeg + else + echo "Cannot proceed with a non-git FFmpeg directory present. Aborting." + exit 4 + fi + fi + fi + # Dependencies + if [[ $distro == "ubuntu" || $distro == "debian" ]]; then + sudo apt update + sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev + elif [[ $distro == "arch" || $distro == "manjaro" || $distro == "endeavouros" ]]; then + sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise + elif [[ $distro == "fedora" ]]; then + sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel + else + echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2 + fi + if [[ ! -d FFmpeg/.git ]]; then + git clone https://github.com/FFmpeg/FFmpeg.git --depth=1 + fi + cd FFmpeg + RN_FLAG="" + # Some FFmpeg versions auto-detect rnnoise without a flag; include the flag only if supported + if ./configure --help | grep -q "librnnoise"; then + RN_FLAG="--enable-librnnoise" + else + echo "[info] configure has no --enable-librnnoise; relying on auto-detection via pkg-config (rnnoise)." >&2 + fi - ./configure \ - --enable-gpl --enable-nonfree \ - --enable-libx264 --enable-libx265 --enable-libvpx --enable-libopus --enable-libmp3lame \ - --enable-libvorbis --enable-libass --enable-fontconfig --enable-libfreetype \ - --enable-librubberband --enable-libsoxr --enable-libzimg --enable-libvmaf \ - --enable-libdav1d --enable-libaom --enable-libsvtav1 \ - ${RN_FLAG} \ - --enable-ffplay --enable-ffprobe - make -j"$(nproc)" - echo "Build complete. You can run ./ffmpeg-build/FFmpeg/ffmpeg from this folder or 'sudo make install' to install system-wide." - set +x - else - echo "Skipped building from source." - fi + ./configure \ + --enable-gpl --enable-nonfree \ + --enable-libx264 --enable-libx265 --enable-libvpx --enable-libopus --enable-libmp3lame \ + --enable-libvorbis --enable-libass --enable-fontconfig --enable-libfreetype \ + --enable-librubberband --enable-libsoxr --enable-libzimg --enable-libvmaf \ + --enable-libdav1d --enable-libaom --enable-libsvtav1 \ + ${RN_FLAG} \ + --enable-ffplay --enable-ffprobe + make -j"$(nproc)" + echo "Build complete. You can run ./ffmpeg-build/FFmpeg/ffmpeg from this folder or 'sudo make install' to install system-wide." + set +x + else + echo "Skipped building from source." + fi } main "$@" diff --git a/scripts/misc/testsAndMisc-bash/install_unity_mcp.sh b/scripts/misc/testsAndMisc-bash/install_unity_mcp.sh index e47e3f9..77c37ea 100755 --- a/scripts/misc/testsAndMisc-bash/install_unity_mcp.sh +++ b/scripts/misc/testsAndMisc-bash/install_unity_mcp.sh @@ -2,6 +2,11 @@ set -euo pipefail +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../../lib/common.sh +source "$SCRIPT_DIR/../../lib/common.sh" + SCRIPT_NAME="$(basename "$0")" RED="\033[31m" @@ -10,185 +15,158 @@ BLUE="\033[34m" RESET="\033[0m" info() { - printf "%b[%s]%b %s\n" "$BLUE" "$SCRIPT_NAME" "$RESET" "$*" + printf "%b[%s]%b %s\n" "$BLUE" "$SCRIPT_NAME" "$RESET" "$*" } warn() { - printf "%b[%s]%b %s\n" "$YELLOW" "$SCRIPT_NAME" "$RESET" "$*" >&2 + printf "%b[%s]%b %s\n" "$YELLOW" "$SCRIPT_NAME" "$RESET" "$*" >&2 } error() { - printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2 -} - -require_command() { - local cmd="$1" - local package_hint="${2:-}" - - if ! command -v "$cmd" > /dev/null 2>&1; then - if [[ -n $package_hint ]]; then - error "Missing command '$cmd'. Try installing the package: $package_hint" - else - error "Missing command '$cmd'." - fi - exit 1 - fi + printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2 } ensure_pacman_packages() { - local packages=("python" "git" "curl" "jq" "code") - local missing=() - for pkg in "${packages[@]}"; do - if ! pacman -Qi "$pkg" > /dev/null 2>&1; then - missing+=("$pkg") - fi - done - - if ((${#missing[@]} > 0)); then - info "Installing required packages with pacman: ${missing[*]}" - sudo pacman -S --needed --noconfirm "${missing[@]}" - else - info "All required pacman packages are already installed." - fi + install_missing_pacman_packages python git curl jq code } install_uv() { - if command -v uv > /dev/null 2>&1; then - info "uv is already installed." - return - fi + if command -v uv >/dev/null 2>&1; then + info "uv is already installed." + return + fi - info "Installing uv toolchain manager via official installer." - curl -LsSf https://astral.sh/uv/install.sh | sh + info "Installing uv toolchain manager via official installer." + curl -LsSf https://astral.sh/uv/install.sh | sh - local local_bin="$HOME/.local/bin" - if [[ :$PATH: != *":$local_bin:"* ]]; then - warn "Adding $local_bin to PATH in ~/.profile and ~/.zshrc. Open a new shell to apply." - printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >> "$HOME/.profile" - printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >> "$HOME/.zshrc" - fi + local local_bin="$HOME/.local/bin" + if [[ :$PATH: != *":$local_bin:"* ]]; then + warn "Adding $local_bin to PATH in ~/.profile and ~/.zshrc. Open a new shell to apply." + printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >>"$HOME/.profile" + printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >>"$HOME/.zshrc" + fi } ensure_unity_hub() { - if command -v unityhub > /dev/null 2>&1; then - info "Unity Hub already installed." - return - fi + if command -v unityhub >/dev/null 2>&1; then + info "Unity Hub already installed." + return + fi - if command -v yay > /dev/null 2>&1; then - info "Installing Unity Hub from AUR using yay." - yay -S --needed --noconfirm unityhub - elif command -v flatpak > /dev/null 2>&1; then - warn "Unity Hub not found. Attempting Flatpak installation." - flatpak install -y com.unity.UnityHub || warn "Flatpak installation failed. Install Unity Hub manually via https://unity.com/download" - else - warn "Unity Hub not found and neither yay nor flatpak is available. Install Unity Hub manually from https://unity.com/download." - fi + if command -v yay >/dev/null 2>&1; then + info "Installing Unity Hub from AUR using yay." + yay -S --needed --noconfirm unityhub + elif command -v flatpak >/dev/null 2>&1; then + warn "Unity Hub not found. Attempting Flatpak installation." + flatpak install -y com.unity.UnityHub || warn "Flatpak installation failed. Install Unity Hub manually via https://unity.com/download" + else + warn "Unity Hub not found and neither yay nor flatpak is available. Install Unity Hub manually from https://unity.com/download." + fi } sync_unity_mcp_repo() { - local data_home="${XDG_DATA_HOME:-$HOME/.local/share}" - local unity_mcp_root="$data_home/UnityMCP" - local repo_dir="$unity_mcp_root/unity-mcp-repo" - local server_link="$unity_mcp_root/UnityMcpServer" - local candidates=( - "UnityMcpServer" - "UnityMcpBridge/UnityMcpServer" - "UnityMcpBridge/UnityMcpServer~" - ) - local server_subdir="" + local data_home="${XDG_DATA_HOME:-$HOME/.local/share}" + local unity_mcp_root="$data_home/UnityMCP" + local repo_dir="$unity_mcp_root/unity-mcp-repo" + local server_link="$unity_mcp_root/UnityMcpServer" + local candidates=( + "UnityMcpServer" + "UnityMcpBridge/UnityMcpServer" + "UnityMcpBridge/UnityMcpServer~" + ) + local server_subdir="" - mkdir -p "$unity_mcp_root" + mkdir -p "$unity_mcp_root" - if [[ -d "$repo_dir/.git" ]]; then - info "Updating existing unity-mcp repository." - git -C "$repo_dir" pull --ff-only - else - info "Cloning unity-mcp repository." - rm -rf "$repo_dir" - git clone --depth=1 https://github.com/CoplayDev/unity-mcp.git "$repo_dir" - fi + if [[ -d "$repo_dir/.git" ]]; then + info "Updating existing unity-mcp repository." + git -C "$repo_dir" pull --ff-only + else + info "Cloning unity-mcp repository." + rm -rf "$repo_dir" + git clone --depth=1 https://github.com/CoplayDev/unity-mcp.git "$repo_dir" + fi - for candidate in "${candidates[@]}"; do - if [[ -d "$repo_dir/$candidate/src" ]]; then - server_subdir="$candidate" - break - fi - done + for candidate in "${candidates[@]}"; do + if [[ -d "$repo_dir/$candidate/src" ]]; then + server_subdir="$candidate" + break + fi + done - if [[ -z $server_subdir ]]; then - error "UnityMcpServer src directory not found. Checked candidates: ${candidates[*]}" - error "Repository layout may have changed. Inspect $repo_dir for the new server location." - exit 1 - fi + if [[ -z $server_subdir ]]; then + error "UnityMcpServer src directory not found. Checked candidates: ${candidates[*]}" + error "Repository layout may have changed. Inspect $repo_dir for the new server location." + exit 1 + fi - ln -sfn "$repo_dir/$server_subdir" "$server_link" - info "UnityMcpServer synchronized at $server_link (source: $server_subdir)" + ln -sfn "$repo_dir/$server_subdir" "$server_link" + info "UnityMcpServer synchronized at $server_link (source: $server_subdir)" } configure_vscode_mcp() { - local data_home="${XDG_DATA_HOME:-$HOME/.local/share}" - local server_src="$data_home/UnityMCP/UnityMcpServer/src" - local mcp_config_dir="$HOME/.config/Code/User" - local mcp_config="$mcp_config_dir/mcp.json" - local tmp + local data_home="${XDG_DATA_HOME:-$HOME/.local/share}" + local server_src="$data_home/UnityMCP/UnityMcpServer/src" + local mcp_config_dir="$HOME/.config/Code/User" + local mcp_config="$mcp_config_dir/mcp.json" + local tmp - if [[ ! -d $server_src ]]; then - error "Server source directory $server_src is missing." - exit 1 - fi + if [[ ! -d $server_src ]]; then + error "Server source directory $server_src is missing." + exit 1 + fi - mkdir -p "$mcp_config_dir" + mkdir -p "$mcp_config_dir" - if [[ ! -f $mcp_config ]]; then - info "Creating new VS Code MCP configuration at $mcp_config" - echo '{}' > "$mcp_config" - else - info "Updating existing VS Code MCP configuration at $mcp_config" - fi + if [[ ! -f $mcp_config ]]; then + info "Creating new VS Code MCP configuration at $mcp_config" + echo '{}' >"$mcp_config" + else + info "Updating existing VS Code MCP configuration at $mcp_config" + fi - tmp="$(mktemp)" + tmp="$(mktemp)" - if ! jq '.' "$mcp_config" > /dev/null 2>&1; then - error "Existing $mcp_config is not valid JSON. Please fix it before running this script again." - exit 1 - fi + if ! jq '.' "$mcp_config" >/dev/null 2>&1; then + error "Existing $mcp_config is not valid JSON. Please fix it before running this script again." + exit 1 + fi - jq \ - --arg path "$server_src" \ - '(.servers //= {}) | + jq \ + --arg path "$server_src" \ + '(.servers //= {}) | .servers.unityMCP = { command: "uv", args: ["--directory", $path, "run", "server.py"], type: "stdio" }' \ - "$mcp_config" > "$tmp" + "$mcp_config" >"$tmp" - mv "$tmp" "$mcp_config" - info "VS Code MCP server configuration updated for UnityMCP." + mv "$tmp" "$mcp_config" + info "VS Code MCP server configuration updated for UnityMCP." } verify_python_version() { - require_command python "python" - local version - version="$( - python - << 'PY' + require_command python "python" + local version + version="$( + python - <<'PY' import sys print("%d.%d.%d" % sys.version_info[:3]) PY - )" - local major minor - IFS='.' read -r major minor _ <<< "$version" - if ((major < 3 || (major == 3 && minor < 12))); then - error "Python 3.12+ is required. Detected version $version. Upgrade python before continuing." - exit 1 - fi - info "Python version $version satisfies requirement (>= 3.12)." + )" + local major minor + IFS='.' read -r major minor _ <<<"$version" + if ((major < 3 || (major == 3 && minor < 12))); then + error "Python 3.12+ is required. Detected version $version. Upgrade python before continuing." + exit 1 + fi + info "Python version $version satisfies requirement (>= 3.12)." } print_next_steps() { - cat << 'EOT' + cat <<'EOT' Next steps: 1. Launch Unity Hub and install a Unity Editor version 2021.3 LTS or newer. @@ -212,22 +190,22 @@ EOT } main() { - if [[ ! -f /etc/arch-release ]]; then - error "This script is intended for Arch Linux." - exit 1 - fi + if [[ ! -f /etc/arch-release ]]; then + error "This script is intended for Arch Linux." + exit 1 + fi - info "Ensuring base dependencies are installed." - require_command sudo "sudo" - require_command pacman "pacman" - ensure_pacman_packages - verify_python_version - install_uv - ensure_unity_hub - sync_unity_mcp_repo - configure_vscode_mcp - print_next_steps - info "Setup complete. Follow the next steps above to finish configuration inside Unity." + info "Ensuring base dependencies are installed." + require_command sudo "sudo" + require_command pacman "pacman" + ensure_pacman_packages + verify_python_version + install_uv + ensure_unity_hub + sync_unity_mcp_repo + configure_vscode_mcp + print_next_steps + info "Setup complete. Follow the next steps above to finish configuration inside Unity." } main "$@" diff --git a/scripts/setup_periodic_system.sh b/scripts/setup_periodic_system.sh index b24066b..267dc3e 100755 --- a/scripts/setup_periodic_system.sh +++ b/scripts/setup_periodic_system.sh @@ -17,16 +17,7 @@ shift "$COMMON_ARGS_SHIFT" # Check for sudo privileges require_root "$@" -echo "Periodic System Setup - Pacman Wrapper & Hosts File" -echo "===================================================" -echo "Current Date: $(date)" -echo "User: $USER" -echo "Original user: $(get_actual_user)" -if [[ $INTERACTIVE_MODE == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" -else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" -fi +print_setup_header "Periodic System Setup - Pacman Wrapper & Hosts File" # Get the directory where this script is located CONFIG_DIR="$(dirname "$SCRIPT_DIR")" diff --git a/scripts/setup_thorium_startup.sh b/scripts/setup_thorium_startup.sh index 451ddd0..9d48d1e 100755 --- a/scripts/setup_thorium_startup.sh +++ b/scripts/setup_thorium_startup.sh @@ -16,16 +16,7 @@ shift "$COMMON_ARGS_SHIFT" # Check for sudo privileges require_root "$@" -echo "Thorium Browser Auto-Startup Setup" -echo "==================================" -echo "Current Date: $(date)" -echo "User: $USER" -echo "Original user: $(get_actual_user)" -if [[ $INTERACTIVE_MODE == "true" ]]; then - echo "Mode: Interactive (prompts enabled)" -else - echo "Mode: Automatic (auto-yes, use --interactive for prompts)" -fi +print_setup_header "Thorium Browser Auto-Startup Setup" # Target URL TARGET_URL="https://www.fitatu.com/app/planner" diff --git a/scripts/utils/convert_video.sh b/scripts/utils/convert_video.sh index d153aaa..a7d1692 100644 --- a/scripts/utils/convert_video.sh +++ b/scripts/utils/convert_video.sh @@ -7,6 +7,11 @@ set -euo pipefail # Convert video files to a target format (mp4 or webm) using ffmpeg. # Accepts either a single video file or a directory (will recurse into subdirectories). +# Source common library +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Default settings TARGET_FORMAT="mp4" CRF="" # Will be set based on format if not specified @@ -17,10 +22,6 @@ TARGET_PATH="" # Video extensions to search for ALL_VIDEO_EXTENSIONS=("mp4" "webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v") -log() { - printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" -} - usage() { cat <&2 exit 1 fi @@ -194,8 +195,8 @@ parse_args() { TARGET_PATH="$1" # Set default CRF based on format if not specified - if [[ -z "$CRF" ]]; then - if [[ "$TARGET_FORMAT" == "mp4" ]]; then + if [[ -z $CRF ]]; then + if [[ $TARGET_FORMAT == "mp4" ]]; then CRF=23 else CRF=30 @@ -207,14 +208,14 @@ main() { ensure_ffmpeg parse_args "$@" - if [[ ! -e "$TARGET_PATH" ]]; then + if [[ ! -e $TARGET_PATH ]]; then echo "Error: Path '$TARGET_PATH' does not exist." >&2 exit 1 fi - if [[ -f "$TARGET_PATH" ]]; then + if [[ -f $TARGET_PATH ]]; then # Single file - if [[ "${TARGET_PATH,,}" == *."$TARGET_FORMAT" ]]; then + if [[ ${TARGET_PATH,,} == *."$TARGET_FORMAT" ]]; then log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping." exit 0 fi @@ -225,7 +226,7 @@ main() { echo "Error: '$TARGET_PATH' is not a recognized video file." >&2 exit 1 fi - elif [[ -d "$TARGET_PATH" ]]; then + elif [[ -d $TARGET_PATH ]]; then process_directory "$TARGET_PATH" else echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2 diff --git a/scripts/utils/image_to_resolution.sh b/scripts/utils/image_to_resolution.sh index 9804638..ee0593e 100755 --- a/scripts/utils/image_to_resolution.sh +++ b/scripts/utils/image_to_resolution.sh @@ -5,12 +5,17 @@ set -euo pipefail +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Default resolution DEFAULT_RESOLUTION="320x240" # Function to display usage usage() { - cat << EOF + cat < [resolution] [output_image] Arguments: @@ -26,22 +31,16 @@ Examples: Note: Requires ImageMagick (convert command) EOF - exit 1 + exit 1 } # Check if ImageMagick is installed -if ! command -v convert &> /dev/null; then - echo "Error: ImageMagick (convert) is not installed." - echo "Install it with:" - echo " Arch Linux: sudo pacman -S imagemagick" - echo " Ubuntu/Debian: sudo apt install imagemagick" - exit 1 -fi +require_imagemagick "convert" || exit 1 # Parse arguments if [[ $# -lt 1 ]]; then - echo "Error: Missing required argument " - usage + echo "Error: Missing required argument " + usage fi INPUT_IMAGE="$1" @@ -50,32 +49,20 @@ OUTPUT_IMAGE="${3:-}" # Validate input image exists if [[ ! -f ${INPUT_IMAGE} ]]; then - echo "Error: Input image '${INPUT_IMAGE}' does not exist." - exit 1 + echo "Error: Input image '${INPUT_IMAGE}' does not exist." + exit 1 fi # Validate resolution format (WIDTHxHEIGHT) -if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then - echo "Error: Invalid resolution format '${RESOLUTION}'" - echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)" - exit 1 +if ! validate_resolution "$RESOLUTION"; then + echo "Error: Invalid resolution format '${RESOLUTION}'" + echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)" + exit 1 fi # Generate output filename if not provided if [[ -z ${OUTPUT_IMAGE} ]]; then - # Extract filename without extension and extension - BASENAME=$(basename "${INPUT_IMAGE}") - FILENAME="${BASENAME%.*}" - EXTENSION="${BASENAME##*.}" - - # If no extension (single name file), default to jpg - if [[ ${FILENAME} == "${EXTENSION}" ]]; then - EXTENSION="jpg" - fi - - # Create output filename with resolution suffix - DIRNAME=$(dirname "${INPUT_IMAGE}") - OUTPUT_IMAGE="${DIRNAME}/${FILENAME}_${RESOLUTION}.${EXTENSION}" + OUTPUT_IMAGE=$(generate_output_filename "${INPUT_IMAGE}" "_${RESOLUTION}") fi # Perform the conversion @@ -83,15 +70,15 @@ echo "Converting '${INPUT_IMAGE}' to ${RESOLUTION}..." echo "Output will be saved to: ${OUTPUT_IMAGE}" if convert "${INPUT_IMAGE}" -resize "${RESOLUTION}!" "${OUTPUT_IMAGE}"; then - echo "✓ Successfully converted image to ${RESOLUTION}" - echo "Output: ${OUTPUT_IMAGE}" + echo "✓ Successfully converted image to ${RESOLUTION}" + echo "Output: ${OUTPUT_IMAGE}" - # Show file sizes - INPUT_SIZE=$(du -h "${INPUT_IMAGE}" | cut -f1) - OUTPUT_SIZE=$(du -h "${OUTPUT_IMAGE}" | cut -f1) - echo "Input size: ${INPUT_SIZE}" - echo "Output size: ${OUTPUT_SIZE}" + # Show file sizes + INPUT_SIZE=$(du -h "${INPUT_IMAGE}" | cut -f1) + OUTPUT_SIZE=$(du -h "${OUTPUT_IMAGE}" | cut -f1) + echo "Input size: ${INPUT_SIZE}" + echo "Output size: ${OUTPUT_SIZE}" else - echo "✗ Error: Conversion failed" - exit 1 + echo "✗ Error: Conversion failed" + exit 1 fi diff --git a/scripts/utils/pdf_to_image.sh b/scripts/utils/pdf_to_image.sh index 393381b..4d35464 100755 --- a/scripts/utils/pdf_to_image.sh +++ b/scripts/utils/pdf_to_image.sh @@ -7,16 +7,17 @@ set -euo pipefail # Convert one or more PDF files to image files using ImageMagick v7 `magick`. # Default output format is jpg, but can be changed with -f. +# Source common library +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + OUTPUT_DIR="" OUTPUT_FORMAT="jpg" PDF_FILES=() -log() { - printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" -} - usage() { - cat << EOF + cat < /dev/null 2>&1; then - echo "Error: 'magick' (ImageMagick v7) is not installed or not in PATH." >&2 - exit 1 - fi + require_imagemagick "magick" || exit 1 } parse_args() { - local opt - OUTPUT_DIR="" - OUTPUT_FORMAT="jpg" - PDF_FILES=() + local opt + OUTPUT_DIR="" + OUTPUT_FORMAT="jpg" + PDF_FILES=() - while getopts ":o:f:h" opt; do - case "$opt" in - o) - OUTPUT_DIR="$OPTARG" - ;; - f) - OUTPUT_FORMAT="$OPTARG" - ;; - h) - usage - exit 0 - ;; - *) - usage - exit 1 - ;; - esac - done + while getopts ":o:f:h" opt; do + case "$opt" in + o) + OUTPUT_DIR="$OPTARG" + ;; + f) + OUTPUT_FORMAT="$OPTARG" + ;; + h) + usage + exit 0 + ;; + *) + usage + exit 1 + ;; + esac + done - shift $((OPTIND - 1)) + shift $((OPTIND - 1)) - if [[ $# -lt 1 ]]; then - echo "Error: at least one PDF file must be specified." >&2 - usage - exit 1 - fi + if [[ $# -lt 1 ]]; then + echo "Error: at least one PDF file must be specified." >&2 + usage + exit 1 + fi - PDF_FILES=("$@") + PDF_FILES=("$@") - if [[ -z ${OUTPUT_DIR:-} ]]; then - OUTPUT_DIR="${PWD}" - fi + if [[ -z ${OUTPUT_DIR:-} ]]; then + OUTPUT_DIR="${PWD}" + fi - if [[ ! -d $OUTPUT_DIR ]]; then - mkdir -p "$OUTPUT_DIR" - fi + if [[ ! -d $OUTPUT_DIR ]]; then + mkdir -p "$OUTPUT_DIR" + fi } convert_pdf() { - local pdf_file="$1" - local base name out_pattern + local pdf_file="$1" + local base name out_pattern - name="$(basename "$pdf_file")" - base="${name%.*}" - out_pattern="${OUTPUT_DIR%/}/${base}_page-" + name="$(basename "$pdf_file")" + base="${name%.*}" + out_pattern="${OUTPUT_DIR%/}/${base}_page-" - log "Converting '$pdf_file' to $OUTPUT_FORMAT using magick -> ${out_pattern}*.${OUTPUT_FORMAT}" - magick -density 300 "$pdf_file" -quality 90 "${out_pattern}%d.${OUTPUT_FORMAT}" + log "Converting '$pdf_file' to $OUTPUT_FORMAT using magick -> ${out_pattern}*.${OUTPUT_FORMAT}" + magick -density 300 "$pdf_file" -quality 90 "${out_pattern}%d.${OUTPUT_FORMAT}" } main() { - ensure_magick - parse_args "$@" + ensure_magick + parse_args "$@" - local pdf - for pdf in "${PDF_FILES[@]}"; do - if [[ ! -f $pdf ]]; then - echo "Warning: '$pdf' is not a regular file, skipping." >&2 - continue - fi + local pdf + for pdf in "${PDF_FILES[@]}"; do + if [[ ! -f $pdf ]]; then + echo "Warning: '$pdf' is not a regular file, skipping." >&2 + continue + fi - convert_pdf "$pdf" - done + convert_pdf "$pdf" + done - log "Done converting PDFs to ${OUTPUT_FORMAT}. Output directory: $OUTPUT_DIR" + log "Done converting PDFs to ${OUTPUT_FORMAT}. Output directory: $OUTPUT_DIR" } main "$@" diff --git a/scripts/utils/root_bl9000.sh b/scripts/utils/root_bl9000.sh index 169b9a9..88bfe08 100755 --- a/scripts/utils/root_bl9000.sh +++ b/scripts/utils/root_bl9000.sh @@ -214,7 +214,7 @@ setup_udev_rules() { # Install MTKClient udev rules if mtkclient is present if [[ -d "${WORK_DIR}/mtkclient" ]]; then log "Installing MTKClient udev rules..." - if [[ -d "$mtk_udev_dir" ]]; then + if [[ -d $mtk_udev_dir ]]; then sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2>/dev/null || warn "Failed to copy MTKClient rules" fi fi @@ -634,7 +634,7 @@ install_mtkclient() { local mtk_dir="${WORK_DIR}/mtkclient" - if [[ -d "$mtk_dir" && -f "$mtk_dir/mtk.py" ]]; then + if [[ -d $mtk_dir && -f "$mtk_dir/mtk.py" ]]; then log "MTKClient already installed at $mtk_dir" return 0 fi @@ -667,7 +667,7 @@ extract_boot_with_mtkclient() { local boot_a_img="$WORK_DIR/boot_a.img" local vbmeta_a_img="$WORK_DIR/vbmeta_a.img" - if [[ ! -d "$mtk_dir" ]]; then + if [[ ! -d $mtk_dir ]]; then error "MTKClient not installed. Run: $SCRIPT_NAME install-mtk" return 1 fi diff --git a/scripts/utils/setup_android_adblock.sh b/scripts/utils/setup_android_adblock.sh index a0b038a..70ac083 100755 --- a/scripts/utils/setup_android_adblock.sh +++ b/scripts/utils/setup_android_adblock.sh @@ -6,43 +6,13 @@ set -euo pipefail SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" # shellcheck source=../lib/common.sh source "$SCRIPT_DIR/../lib/common.sh" +# shellcheck source=../lib/android.sh +source "$SCRIPT_DIR/../lib/android.sh" # Re-run with sudo if needed for reading /etc/hosts -if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then - exec sudo -E bash "$0" "$@" -fi +require_hosts_readable "$@" -WORK_DIR="${HOME}/.cache/android-adblock" -ensure_dir "$WORK_DIR" - -die() { - echo "[ERROR] $*" >&2 - exit 1 -} - -print_header() { - echo - echo "========================================" - echo " $1" - echo "========================================" - echo -} - -check_device() { - log "Checking device connection..." - if ! adb devices | grep -q "device$"; then - die "No device connected. Enable USB debugging and connect your phone." - fi - log "Device connected" -} - -check_root() { - log "Checking root access..." - if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then - die "Root access not available. Make sure Magisk is installed and grant root to Shell." - fi - log "Root access confirmed" -} +WORK_DIR="$ANDROID_WORK_DIR" install_adaway() { print_header "Installing AdAway" diff --git a/scripts/utils/toggle_window_manager.sh b/scripts/utils/toggle_window_manager.sh index 5b8c35a..5185c15 100755 --- a/scripts/utils/toggle_window_manager.sh +++ b/scripts/utils/toggle_window_manager.sh @@ -2,64 +2,42 @@ set -euo pipefail +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Configuration ----------------------------------------------------------------- TARGET_SESSION_NAME="Xfce Session" TARGET_PACKAGES=( - xfwm4 # Compositing window manager with XFCE integration - xfce4-session # Provides the Xfce session entry for display managers - xfce4-panel # Panel with system tray support - xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.) - xfce4-terminal # Handy default terminal for the new environment + xfwm4 # Compositing window manager with XFCE integration + xfce4-session # Provides the Xfce session entry for display managers + xfce4-panel # Panel with system tray support + xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.) + xfce4-terminal # Handy default terminal for the new environment ) # Utility functions -------------------------------------------------------------- info() { echo "[INFO] $*"; } warn() { echo "[WARN] $*" >&2; } error() { - echo "[ERROR] $*" >&2 - exit 1 -} - -require_command() { - local cmd="$1" pkg_hint="${2:-}" - if ! command -v "$cmd" > /dev/null 2>&1; then - if [[ -n $pkg_hint ]]; then - warn "Install '$pkg_hint' to obtain the '$cmd' command." - fi - error "Required command '$cmd' not found." - fi + echo "[ERROR] $*" >&2 + exit 1 } ensure_pacman() { - require_command pacman "pacman" - if ! grep -qi "arch" /etc/os-release 2> /dev/null; then - warn "This script was designed for Arch Linux; continuing anyway." - fi + require_command pacman "pacman" || error "Required command 'pacman' not found." + if ! grep -qi "arch" /etc/os-release 2>/dev/null; then + warn "This script was designed for Arch Linux; continuing anyway." + fi } install_packages() { - local missing=() - for pkg in "${TARGET_PACKAGES[@]}"; do - if ! pacman -Qi "$pkg" > /dev/null 2>&1; then - missing+=("$pkg") - fi - done - - if [[ ${#missing[@]} -eq 0 ]]; then - info "All target packages are already installed." - return - fi - - if ! command -v sudo > /dev/null 2>&1; then - error "sudo is required to install packages. Install sudo or run this script as root." - fi - - info "Installing missing packages: ${missing[*]}" - sudo pacman -S --needed --noconfirm "${missing[@]}" + install_missing_pacman_packages "${TARGET_PACKAGES[@]}" } print_post_install_tips() { - cat << EOF + cat < /dev/null 2>&1; then - info "Terminating current session (ID: $session_id) via loginctl." - loginctl terminate-session "$session_id" - return - fi + if [[ -n $session_id ]] && loginctl show-session "$session_id" >/dev/null 2>&1; then + info "Terminating current session (ID: $session_id) via loginctl." + loginctl terminate-session "$session_id" + return + fi - if loginctl list-sessions 2> /dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then - info "Terminating all sessions for user '$USER' via loginctl." - loginctl terminate-user "$USER" - return - fi + if loginctl list-sessions 2>/dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then + info "Terminating all sessions for user '$USER' via loginctl." + loginctl terminate-user "$USER" + return + fi - warn "loginctl could not terminate the session; attempting fallback logout." - pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually." + warn "loginctl could not terminate the session; attempting fallback logout." + pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually." } main() { - ensure_pacman - install_packages - print_post_install_tips + ensure_pacman + install_packages + print_post_install_tips - # Give the user a moment to read the instructions before logging out. - logout_user + # Give the user a moment to read the instructions before logging out. + logout_user } main "$@" diff --git a/scripts/utils/txt_to_image.sh b/scripts/utils/txt_to_image.sh index 627021f..5a02f60 100755 --- a/scripts/utils/txt_to_image.sh +++ b/scripts/utils/txt_to_image.sh @@ -6,12 +6,17 @@ set -euo pipefail +# Source common library for shared functions +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + # Default resolution DEFAULT_RESOLUTION="320x240" # Function to display usage usage() { - cat << EOF + cat < [resolution] [output_prefix] Arguments: @@ -26,26 +31,16 @@ Examples: Note: Requires ImageMagick (magick or convert command) EOF - exit 1 + exit 1 } # Check if ImageMagick is installed and determine which command to use -if command -v magick &> /dev/null; then - MAGICK_CMD="magick" -elif command -v convert &> /dev/null; then - MAGICK_CMD="convert" -else - echo "Error: ImageMagick is not installed." - echo "Install it with:" - echo " Arch Linux: sudo pacman -S imagemagick" - echo " Ubuntu/Debian: sudo apt install imagemagick" - exit 1 -fi +require_imagemagick || exit 1 # Parse arguments if [[ $# -lt 1 ]]; then - echo "Error: Missing required argument " - usage + echo "Error: Missing required argument " + usage fi INPUT_FILE="$1" @@ -54,15 +49,15 @@ OUTPUT_PREFIX="${3:-}" # Validate input file exists if [[ ! -f ${INPUT_FILE} ]]; then - echo "Error: Input file '${INPUT_FILE}' does not exist." - exit 1 + echo "Error: Input file '${INPUT_FILE}' does not exist." + exit 1 fi # Validate resolution format (WIDTHxHEIGHT) -if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then - echo "Error: Invalid resolution format '${RESOLUTION}'" - echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)" - exit 1 +if ! validate_resolution "$RESOLUTION"; then + echo "Error: Invalid resolution format '${RESOLUTION}'" + echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)" + exit 1 fi # Extract width and height @@ -72,22 +67,22 @@ HEIGHT=$(echo "${RESOLUTION}" | cut -d'x' -f2) # Calculate font size based on resolution FONT_SIZE=$((WIDTH / 30)) if [[ ${FONT_SIZE} -lt 8 ]]; then - FONT_SIZE=8 + FONT_SIZE=8 fi # Generate output prefix if not provided if [[ -z ${OUTPUT_PREFIX} ]]; then - BASENAME=$(basename "${INPUT_FILE}") - FILENAME="${BASENAME%.*}" - DIRNAME=$(dirname "${INPUT_FILE}") - OUTPUT_PREFIX="${DIRNAME}/${FILENAME}" + BASENAME=$(basename "${INPUT_FILE}") + FILENAME="${BASENAME%.*}" + DIRNAME=$(dirname "${INPUT_FILE}") + OUTPUT_PREFIX="${DIRNAME}/${FILENAME}" fi # Calculate lines per image based on resolution and font size # Rough estimate: height / (font_size * 1.5) for line spacing LINES_PER_IMAGE=$((HEIGHT / (FONT_SIZE * 3 / 2))) if [[ ${LINES_PER_IMAGE} -lt 5 ]]; then - LINES_PER_IMAGE=5 + LINES_PER_IMAGE=5 fi echo "Converting text file to image(s)..." @@ -96,7 +91,7 @@ echo "Font size: ${FONT_SIZE}" echo "Estimated lines per image: ${LINES_PER_IMAGE}" # Read the file and count total lines -mapfile -t LINES < "${INPUT_FILE}" +mapfile -t LINES <"${INPUT_FILE}" TOTAL_LINES=${#LINES[@]} echo "Total lines in file: ${TOTAL_LINES}" @@ -113,53 +108,53 @@ trap 'rm -rf ${TEMP_DIR}' EXIT # Split text into chunks and create images IMAGE_COUNT=0 for ((i = 0; i < TOTAL_LINES; i += LINES_PER_IMAGE)); do - IMAGE_COUNT=$((IMAGE_COUNT + 1)) + IMAGE_COUNT=$((IMAGE_COUNT + 1)) - # Calculate end line for this chunk - END_LINE=$((i + LINES_PER_IMAGE)) - if [[ ${END_LINE} -gt ${TOTAL_LINES} ]]; then - END_LINE=${TOTAL_LINES} - fi + # Calculate end line for this chunk + END_LINE=$((i + LINES_PER_IMAGE)) + if [[ ${END_LINE} -gt ${TOTAL_LINES} ]]; then + END_LINE=${TOTAL_LINES} + fi - # Create chunk file - CHUNK_FILE="${TEMP_DIR}/chunk_${IMAGE_COUNT}.txt" - for ((j = i; j < END_LINE; j++)); do - echo "${LINES[$j]}" >> "${CHUNK_FILE}" - done + # Create chunk file + CHUNK_FILE="${TEMP_DIR}/chunk_${IMAGE_COUNT}.txt" + for ((j = i; j < END_LINE; j++)); do + echo "${LINES[$j]}" >>"${CHUNK_FILE}" + done - # Determine output filename - if [[ ${NUM_IMAGES} -eq 1 ]]; then - OUTPUT_FILE="${OUTPUT_PREFIX}.png" - else - OUTPUT_FILE="${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png" - fi + # Determine output filename + if [[ ${NUM_IMAGES} -eq 1 ]]; then + OUTPUT_FILE="${OUTPUT_PREFIX}.png" + else + OUTPUT_FILE="${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png" + fi - echo " Creating image ${IMAGE_COUNT}/${NUM_IMAGES}: ${OUTPUT_FILE}" + echo " Creating image ${IMAGE_COUNT}/${NUM_IMAGES}: ${OUTPUT_FILE}" - # Create image from text - # Using label: instead of caption: for better control - if ${MAGICK_CMD} -size "${WIDTH}x${HEIGHT}" \ - -background white \ - -fill black \ - -font "DejaVu-Sans-Mono" \ - -pointsize "${FONT_SIZE}" \ - -gravity northwest \ - label:@"${CHUNK_FILE}" \ - -extent "${WIDTH}x${HEIGHT}" \ - "${OUTPUT_FILE}"; then - OUTPUT_SIZE=$(du -h "${OUTPUT_FILE}" | cut -f1) - echo " ✓ Created: ${OUTPUT_FILE} (${OUTPUT_SIZE})" - else - echo " ✗ Failed to create: ${OUTPUT_FILE}" - exit 1 - fi + # Create image from text + # Using label: instead of caption: for better control + if ${MAGICK_CMD} -size "${WIDTH}x${HEIGHT}" \ + -background white \ + -fill black \ + -font "DejaVu-Sans-Mono" \ + -pointsize "${FONT_SIZE}" \ + -gravity northwest \ + label:@"${CHUNK_FILE}" \ + -extent "${WIDTH}x${HEIGHT}" \ + "${OUTPUT_FILE}"; then + OUTPUT_SIZE=$(du -h "${OUTPUT_FILE}" | cut -f1) + echo " ✓ Created: ${OUTPUT_FILE} (${OUTPUT_SIZE})" + else + echo " ✗ Failed to create: ${OUTPUT_FILE}" + exit 1 + fi done echo "" echo "✓ Successfully created ${IMAGE_COUNT} image(s)" echo "Output files:" if [[ ${NUM_IMAGES} -eq 1 ]]; then - echo " ${OUTPUT_PREFIX}.png" + echo " ${OUTPUT_PREFIX}.png" else - echo " ${OUTPUT_PREFIX}_001.png to ${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png" + echo " ${OUTPUT_PREFIX}_001.png to ${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png" fi diff --git a/scripts/utils/update_android_hosts.sh b/scripts/utils/update_android_hosts.sh index 10b19c3..c006686 100755 --- a/scripts/utils/update_android_hosts.sh +++ b/scripts/utils/update_android_hosts.sh @@ -6,31 +6,21 @@ set -euo pipefail SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" # shellcheck source=../lib/common.sh source "$SCRIPT_DIR/../lib/common.sh" +# shellcheck source=../lib/android.sh +source "$SCRIPT_DIR/../lib/android.sh" # Re-run with sudo if needed for reading /etc/hosts -if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then - exec sudo -E bash "$0" "$@" -fi +require_hosts_readable "$@" -WORK_DIR="${HOME}/.cache/android-adblock" -ensure_dir "$WORK_DIR" - -die() { - echo "[ERROR] $*" >&2 - exit 1 -} +WORK_DIR="$ANDROID_WORK_DIR" log "Updating Android hosts file from Linux configuration..." # Check device connection -if ! adb devices | grep -q "device$"; then - die "No device connected. Enable USB debugging and connect your phone." -fi +check_adb_device # Check root access -if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then - die "Root access not available. Make sure Magisk is installed and grant root to Shell." -fi +check_adb_root # Use the StevenBlack cache or /etc/hosts HOSTS_FILE="$WORK_DIR/hosts"