mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 12:03:03 +02:00
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)
This commit is contained in:
parent
3e336d4958
commit
5b032891c5
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
91
hosts/guard/pacman-hooks/hosts-guard-common.sh
Normal file
91
hosts/guard/pacman-hooks/hosts-guard-common.sh
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
1793
report/jscpd-report.json
Normal file
1793
report/jscpd-report.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -120,6 +120,48 @@ declare -A SERVICE_STATUS
|
||||
ISSUES_FOUND=0
|
||||
FIXES_APPLIED=0
|
||||
|
||||
######################################################################
|
||||
# Report issues and optionally run fix script
|
||||
# Usage: report_and_fix issues_array status_var status_key fix_note setup_script verify_service [args...]
|
||||
######################################################################
|
||||
report_and_fix() {
|
||||
local -n _issues=$1
|
||||
local -n _status=$2
|
||||
local status_key="$3"
|
||||
local fix_note="$4"
|
||||
local setup_script="$5"
|
||||
local verify_service="${6:-}"
|
||||
shift 6
|
||||
local script_args=("$@")
|
||||
|
||||
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 "$fix_note"
|
||||
if [[ -f $setup_script ]]; then
|
||||
run bash "$setup_script" "${script_args[@]}"
|
||||
((FIXES_APPLIED++)) || true
|
||||
# Re-verify after fix
|
||||
if [[ $DRY_RUN -eq 0 && -n $verify_service ]] && systemctl is-enabled "$verify_service" &>/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 </etc/hosts) -lt 100 ]]; then
|
||||
note "Installing hosts file..."
|
||||
if [[ -f "$HOSTS_INSTALL_SCRIPT" ]]; then
|
||||
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
||||
run bash "$HOSTS_INSTALL_SCRIPT"
|
||||
((FIXES_APPLIED++)) || true
|
||||
else
|
||||
@ -528,7 +502,7 @@ check_hosts() {
|
||||
# Run hosts guard setup
|
||||
if ! systemctl is-enabled hosts-guard.path &>/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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 domain="coder" rights="none" pattern="SVG" \/>/<policy domain="coder" rights="read|write" pattern="SVG" \/>/' "$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
|
||||
|
||||
@ -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" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=ActivityWatch
|
||||
@ -221,60 +219,60 @@ Terminal=false
|
||||
Categories=Utility;
|
||||
EOF
|
||||
|
||||
# Set proper ownership if running as root
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file"
|
||||
fi
|
||||
# Set proper ownership if running as root
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file"
|
||||
fi
|
||||
|
||||
echo "✓ Created XDG autostart entry: $desktop_file"
|
||||
echo "✓ Created XDG autostart entry: $desktop_file"
|
||||
|
||||
# Method 2: i3 config autostart (specific to i3)
|
||||
if [[ -f $i3_config ]]; then
|
||||
# Check if autostart entry already exists
|
||||
if ! grep -q "aw-qt" "$i3_config"; then
|
||||
# Add autostart entry to i3 config
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
# Running as root
|
||||
sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config'
|
||||
# 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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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
|
||||
|
||||
50
scripts/lib/android.sh
Normal file
50
scripts/lib/android.sh
Normal file
@ -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
|
||||
}
|
||||
@ -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}"
|
||||
}
|
||||
|
||||
@ -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 <<EOF
|
||||
Usage: $(basename "$0") [options]
|
||||
|
||||
Options:
|
||||
@ -47,7 +40,9 @@ Options:
|
||||
--install-only Only install linters, do not scan
|
||||
--list-only Only list discovered shell files, do not run linters
|
||||
--verbose Print additional details while running
|
||||
-h, --help Show this helpLinters used:
|
||||
-h, --help Show this help
|
||||
|
||||
Linters used:
|
||||
Required: shellcheck, shfmt
|
||||
Optional (if available): checkbashisms, bashate
|
||||
Syntax checks: bash -n, zsh -n (if installed), sh/dash -n
|
||||
@ -55,120 +50,120 @@ EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--path)
|
||||
ROOT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-install)
|
||||
SKIP_INSTALL="true"
|
||||
shift
|
||||
;;
|
||||
--install-only)
|
||||
INSTALL_ONLY="true"
|
||||
shift
|
||||
;;
|
||||
--list-only)
|
||||
LIST_ONLY="true"
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE="true"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
case "$1" in
|
||||
--path)
|
||||
ROOT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-install)
|
||||
SKIP_INSTALL="true"
|
||||
shift
|
||||
;;
|
||||
--install-only)
|
||||
INSTALL_ONLY="true"
|
||||
shift
|
||||
;;
|
||||
--list-only)
|
||||
LIST_ONLY="true"
|
||||
shift
|
||||
;;
|
||||
--verbose)
|
||||
VERBOSE="true"
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! -d $ROOT_DIR ]]; then
|
||||
log_error "Path not found: $ROOT_DIR"
|
||||
exit 2
|
||||
log_error "Path not found: $ROOT_DIR"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
is_cmd() { command -v "$1" > /dev/null 2>&1; }
|
||||
is_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
is_arch() { is_cmd pacman; }
|
||||
have_aur_helper() { is_cmd yay || is_cmd paru; }
|
||||
|
||||
install_if_missing() {
|
||||
local pkg cmd
|
||||
pkg="$1"
|
||||
cmd="$2"
|
||||
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
|
||||
|
||||
@ -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 <<EOF
|
||||
fix_thorium_unity.sh - Auto-allow unityhub:// from Unity origins in Thorium/Chromium
|
||||
|
||||
Options:
|
||||
@ -49,52 +44,52 @@ EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--policy)
|
||||
DO_POLICY=true
|
||||
shift
|
||||
;;
|
||||
--set-default)
|
||||
SET_DEFAULT=true
|
||||
shift
|
||||
;;
|
||||
--restart)
|
||||
DO_RESTART=true
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$1" in
|
||||
--policy)
|
||||
DO_POLICY=true
|
||||
shift
|
||||
;;
|
||||
--set-default)
|
||||
SET_DEFAULT=true
|
||||
shift
|
||||
;;
|
||||
--restart)
|
||||
DO_RESTART=true
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
ensure_sudo() {
|
||||
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
|
||||
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.
|
||||
|
||||
@ -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 <<EOF
|
||||
${SCRIPT_NAME} - Fix Unity Hub sign-in by registering unityhub:// URL handler
|
||||
|
||||
Options:
|
||||
@ -52,158 +47,158 @@ AUTO_INSTALL=false
|
||||
RUN_TEST=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-y | --yes)
|
||||
AUTO_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--test)
|
||||
RUN_TEST=true
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "$1" in
|
||||
-y | --yes)
|
||||
AUTO_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--test)
|
||||
RUN_TEST=true
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" > /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" <<DESK
|
||||
[Desktop Entry]
|
||||
Name=Unity Hub URL Handler
|
||||
Comment=Handle unityhub:// links for Unity Hub sign-in
|
||||
@ -216,82 +211,82 @@ StartupWMClass=Unity Hub
|
||||
MimeType=x-scheme-handler/unityhub;x-scheme-handler/unity;
|
||||
NoDisplay=true
|
||||
DESK
|
||||
log_ok "Desktop entry created/updated."
|
||||
echo "$dest"
|
||||
log_ok "Desktop entry created/updated."
|
||||
echo "$dest"
|
||||
}
|
||||
|
||||
register_mime_handler() {
|
||||
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
|
||||
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:-<none>}"
|
||||
log_info "Current handler (unity): ${cur2:-<none>}"
|
||||
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:-<none>}"
|
||||
log_info "Current handler (unity): ${cur2:-<none>}"
|
||||
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 "$@"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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")"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 <<EOF
|
||||
Usage:
|
||||
@ -56,7 +57,7 @@ get_video_extensions_except() {
|
||||
local exclude="$1"
|
||||
local exts=()
|
||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||
if [[ "${ext,,}" != "${exclude,,}" ]]; then
|
||||
if [[ ${ext,,} != "${exclude,,}" ]]; then
|
||||
exts+=("$ext")
|
||||
fi
|
||||
done
|
||||
@ -69,7 +70,7 @@ is_video_file() {
|
||||
ext="${ext,,}" # lowercase
|
||||
|
||||
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||
if [[ "$ext" == "${video_ext,,}" ]]; then
|
||||
if [[ $ext == "${video_ext,,}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
@ -81,7 +82,7 @@ convert_video() {
|
||||
local output_file="${input_file%.*}.${TARGET_FORMAT}"
|
||||
|
||||
# Skip if output already exists
|
||||
if [[ -f "$output_file" ]]; then
|
||||
if [[ -f $output_file ]]; then
|
||||
log "Skipping '$input_file': output '$output_file' already exists"
|
||||
return 0
|
||||
fi
|
||||
@ -91,12 +92,12 @@ convert_video() {
|
||||
local ffmpeg_args=()
|
||||
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
|
||||
|
||||
if [[ "$TARGET_FORMAT" == "mp4" ]]; then
|
||||
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
||||
# H.264 codec for video and AAC for audio (maximum compatibility)
|
||||
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
|
||||
ffmpeg_args+=(-c:a aac -b:a 192k)
|
||||
ffmpeg_args+=(-movflags +faststart)
|
||||
elif [[ "$TARGET_FORMAT" == "webm" ]]; then
|
||||
elif [[ $TARGET_FORMAT == "webm" ]]; then
|
||||
# VP9 codec for video and Opus for audio
|
||||
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
|
||||
ffmpeg_args+=(-c:a libopus -b:a 128k)
|
||||
@ -107,13 +108,13 @@ convert_video() {
|
||||
if ffmpeg "${ffmpeg_args[@]}"; then
|
||||
log "Successfully converted '$input_file'"
|
||||
|
||||
if [[ "$DELETE_ORIGINAL" == true ]]; then
|
||||
if [[ $DELETE_ORIGINAL == true ]]; then
|
||||
log "Deleting original: '$input_file'"
|
||||
rm "$input_file"
|
||||
fi
|
||||
else
|
||||
log "Error converting '$input_file'"
|
||||
[[ -f "$output_file" ]] && rm "$output_file"
|
||||
[[ -f $output_file ]] && rm "$output_file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@ -129,8 +130,8 @@ process_directory() {
|
||||
local find_args=(-type f \()
|
||||
local first=true
|
||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||
if [[ "${ext,,}" != "${TARGET_FORMAT,,}" ]]; then
|
||||
if [[ "$first" == true ]]; then
|
||||
if [[ ${ext,,} != "${TARGET_FORMAT,,}" ]]; then
|
||||
if [[ $first == true ]]; then
|
||||
first=false
|
||||
else
|
||||
find_args+=(-o)
|
||||
@ -159,7 +160,7 @@ parse_args() {
|
||||
case "$opt" in
|
||||
f)
|
||||
TARGET_FORMAT="${OPTARG,,}"
|
||||
if [[ "$TARGET_FORMAT" != "mp4" && "$TARGET_FORMAT" != "webm" ]]; then
|
||||
if [[ $TARGET_FORMAT != "mp4" && $TARGET_FORMAT != "webm" ]]; then
|
||||
echo "Error: Format must be 'mp4' or 'webm'" >&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
|
||||
|
||||
@ -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 <<EOF
|
||||
Usage: $0 <input_image> [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 <input_image>"
|
||||
usage
|
||||
echo "Error: Missing required argument <input_image>"
|
||||
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
|
||||
|
||||
@ -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 <<EOF
|
||||
Usage:
|
||||
$(basename "$0") [OPTIONS] PDF_FILE [PDF_FILE...]
|
||||
|
||||
@ -35,83 +36,80 @@ EOF
|
||||
}
|
||||
|
||||
ensure_magick() {
|
||||
if ! command -v magick > /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 "$@"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 <<EOF
|
||||
|
||||
------------------------------------------------------------------------
|
||||
XFCE session installed.
|
||||
@ -76,31 +54,31 @@ EOF
|
||||
}
|
||||
|
||||
logout_user() {
|
||||
local session_id="${XDG_SESSION_ID:-}"
|
||||
local session_id="${XDG_SESSION_ID:-}"
|
||||
|
||||
if [[ -n $session_id ]] && loginctl show-session "$session_id" > /dev/null 2>&1; then
|
||||
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 "$@"
|
||||
|
||||
@ -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 <<EOF
|
||||
Usage: $0 <input_text_file> [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 <input_text_file>"
|
||||
usage
|
||||
echo "Error: Missing required argument <input_text_file>"
|
||||
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
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user