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
|
||||
|
||||
@ -46,13 +46,13 @@ install_from_aur() {
|
||||
fi
|
||||
cd "$repo_dir" || return 1
|
||||
|
||||
if pacman -Qi "$pkg_name" > /dev/null 2>&1; then
|
||||
if pacman -Qi "$pkg_name" >/dev/null 2>&1; then
|
||||
echo "$pkg_name is already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Cleaning old package artifacts to avoid duplicate -U targets"
|
||||
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true
|
||||
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true
|
||||
|
||||
echo "Building $pkg_name (clean build)"
|
||||
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
|
||||
@ -75,11 +75,22 @@ install_from_aur() {
|
||||
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
|
||||
: >failed.txt
|
||||
: >done.txt
|
||||
|
||||
while IFS= read -r pkg_name; do
|
||||
if [ -z "$pkg_name" ]; then
|
||||
@ -100,25 +111,17 @@ process_packages() {
|
||||
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
|
||||
echo "$pkg_name" >>done.txt
|
||||
else
|
||||
echo "$pkg_name" >> failed.txt
|
||||
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
|
||||
try_aur_install "$repo_url" "$pkg_name"
|
||||
fi
|
||||
else
|
||||
if install_from_aur "$repo_url" "$pkg_name"; then
|
||||
echo "$pkg_name" >> done.txt
|
||||
else
|
||||
echo "$pkg_name" >> failed.txt
|
||||
try_aur_install "$repo_url" "$pkg_name"
|
||||
fi
|
||||
fi
|
||||
done < "$file_path"
|
||||
done <"$file_path"
|
||||
}
|
||||
|
||||
sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak
|
||||
@ -129,19 +132,19 @@ sudo cp ./pacman.conf /etc/pacman.conf
|
||||
# sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf
|
||||
# mkinitcpio -P
|
||||
# Reflector install / service management (idempotent & resilient)
|
||||
if pacman -Qi reflector > /dev/null 2>&1; then
|
||||
if pacman -Qi reflector >/dev/null 2>&1; then
|
||||
echo "reflector already installed"
|
||||
else
|
||||
yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)"
|
||||
fi
|
||||
# Prefer timer over service (Arch default)
|
||||
if systemctl list-unit-files | grep -q '^reflector.timer'; then
|
||||
if systemctl is-enabled reflector.timer > /dev/null 2>&1; then
|
||||
if systemctl is-enabled reflector.timer >/dev/null 2>&1; then
|
||||
echo "reflector.timer already enabled"
|
||||
else
|
||||
sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer"
|
||||
fi
|
||||
if systemctl is-active reflector.timer > /dev/null 2>&1; then
|
||||
if systemctl is-active reflector.timer >/dev/null 2>&1; then
|
||||
echo "reflector.timer already active"
|
||||
else
|
||||
if ! sudo systemctl start reflector.timer; then
|
||||
@ -149,12 +152,12 @@ if systemctl list-unit-files | grep -q '^reflector.timer'; then
|
||||
fi
|
||||
fi
|
||||
elif systemctl list-unit-files | grep -q '^reflector.service'; then
|
||||
if systemctl is-enabled reflector.service > /dev/null 2>&1; then
|
||||
if systemctl is-enabled reflector.service >/dev/null 2>&1; then
|
||||
echo "reflector.service already enabled"
|
||||
else
|
||||
sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service"
|
||||
fi
|
||||
if systemctl is-active reflector.service > /dev/null 2>&1; then
|
||||
if systemctl is-active reflector.service >/dev/null 2>&1; then
|
||||
echo "reflector.service already running"
|
||||
else
|
||||
if ! sudo systemctl start reflector.service; then
|
||||
@ -172,7 +175,19 @@ while IFS= read -r line; do
|
||||
aur_packages+=("$line")
|
||||
aur_package_names+=("${line%% *}")
|
||||
fi
|
||||
done < "aur_packages.txt"
|
||||
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
|
||||
@ -181,7 +196,7 @@ while IFS= read -r line; do
|
||||
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
|
||||
pacman_packages+=("$line")
|
||||
fi
|
||||
done < "pacman_packages.txt"
|
||||
done <"pacman_packages.txt"
|
||||
|
||||
for pkg in "${pacman_packages[@]}"; do
|
||||
# Skip NVIDIA packages if GPU is not NVIDIA
|
||||
@ -191,21 +206,15 @@ for pkg in "${pacman_packages[@]}"; do
|
||||
fi
|
||||
# Check for texlive subpackages
|
||||
if [ "$pkg" == "texlive" ]; then
|
||||
sub_pkgs=(
|
||||
# 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
|
||||
)
|
||||
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
|
||||
if all_subpackages_installed texlive_sub_pkgs; then
|
||||
echo "All texlive subpackages are installed, skipping texlive"
|
||||
continue
|
||||
fi
|
||||
@ -213,27 +222,21 @@ for pkg in "${pacman_packages[@]}"; do
|
||||
|
||||
# Check for texlive-lang subpackages
|
||||
if [ "$pkg" == "texlive-lang" ]; then
|
||||
sub_pkgs=(
|
||||
# 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
|
||||
)
|
||||
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
|
||||
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 ! pacman -Qi "$pkg" &>/dev/null; then
|
||||
if ! printf '%s
|
||||
' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then
|
||||
yes | sudo pacman -Sy --noconfirm "$pkg"
|
||||
@ -244,7 +247,7 @@ for pkg in "${pacman_packages[@]}"; do
|
||||
echo "$pkg is already installed"
|
||||
fi
|
||||
done
|
||||
if ! command -v nvm &> /dev/null; then
|
||||
if ! command -v nvm &>/dev/null; then
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||
else
|
||||
echo "nvm is already installed"
|
||||
@ -256,7 +259,7 @@ if [ -s "$NVM_DIR/nvm.sh" ]; then
|
||||
else
|
||||
echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2
|
||||
fi
|
||||
if command -v nvm &> /dev/null; then
|
||||
if command -v nvm &>/dev/null; then
|
||||
nvm i v18.20.5
|
||||
nvm install --lts
|
||||
else
|
||||
|
||||
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)
|
||||
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
|
||||
# Cleanup: remove any installed blocked packages
|
||||
function remove_installed_blocked_packages() {
|
||||
remove_installed_packages_matching is_blocked_package_name "Policy"
|
||||
}
|
||||
|
||||
if [[ ${#to_remove[@]} -eq 0 ]]; then
|
||||
# Cleanup: remove any installed greylisted packages
|
||||
function remove_installed_greylisted_packages() {
|
||||
remove_installed_packages_matching is_greylisted_package_name "Greylist"
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
done
|
||||
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,6 +5,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 check and request sudo privileges for package installation
|
||||
check_sudo() {
|
||||
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
|
||||
@ -14,20 +19,13 @@ check_sudo() {
|
||||
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"
|
||||
|
||||
@ -38,7 +36,7 @@ check_activitywatch_installed() {
|
||||
echo "========================================"
|
||||
|
||||
# Check if activitywatch-bin is installed via pacman
|
||||
if pacman -Qi activitywatch-bin &> /dev/null; then
|
||||
if pacman -Qi activitywatch-bin &>/dev/null; then
|
||||
echo "✓ activitywatch-bin package is installed"
|
||||
return 0
|
||||
fi
|
||||
@ -78,7 +76,7 @@ install_activitywatch() {
|
||||
local helper_found=""
|
||||
|
||||
for helper in "${aur_helpers[@]}"; do
|
||||
if command -v "$helper" &> /dev/null; then
|
||||
if command -v "$helper" &>/dev/null; then
|
||||
helper_found="$helper"
|
||||
break
|
||||
fi
|
||||
@ -110,7 +108,7 @@ install_activitywatch_manual() {
|
||||
cd "$temp_dir"
|
||||
|
||||
# Download PKGBUILD
|
||||
if command -v git &> /dev/null; then
|
||||
if command -v git &>/dev/null; then
|
||||
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
|
||||
else
|
||||
echo "Installing git..."
|
||||
@ -133,13 +131,13 @@ check_activitywatch_running() {
|
||||
echo "=================================="
|
||||
|
||||
# Check for aw-qt process
|
||||
if pgrep -f "aw-qt" > /dev/null; then
|
||||
if pgrep -f "aw-qt" >/dev/null; then
|
||||
echo "✓ ActivityWatch (aw-qt) is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for aw-server process
|
||||
if pgrep -f "aw-server" > /dev/null; then
|
||||
if pgrep -f "aw-server" >/dev/null; then
|
||||
echo "✓ ActivityWatch server is running"
|
||||
return 0
|
||||
fi
|
||||
@ -157,7 +155,7 @@ start_activitywatch() {
|
||||
# Find aw-qt executable
|
||||
local aw_qt_path=""
|
||||
|
||||
if command -v aw-qt &> /dev/null; then
|
||||
if command -v aw-qt &>/dev/null; then
|
||||
aw_qt_path="$(which aw-qt)"
|
||||
elif [[ -x "/usr/bin/aw-qt" ]]; then
|
||||
aw_qt_path="/usr/bin/aw-qt"
|
||||
@ -181,7 +179,7 @@ start_activitywatch() {
|
||||
# Give it time to start
|
||||
sleep 3
|
||||
|
||||
if check_activitywatch_running > /dev/null 2>&1; then
|
||||
if check_activitywatch_running >/dev/null 2>&1; then
|
||||
echo "✓ ActivityWatch started successfully"
|
||||
else
|
||||
echo "! ActivityWatch may be starting (check system tray)"
|
||||
@ -206,7 +204,7 @@ setup_autostart() {
|
||||
fi
|
||||
|
||||
# Create desktop file for autostart
|
||||
cat > "$desktop_file" << EOF
|
||||
cat >"$desktop_file" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=ActivityWatch
|
||||
@ -245,7 +243,7 @@ EOF"
|
||||
printf '\n'
|
||||
printf '# Auto-start ActivityWatch\n'
|
||||
printf 'exec --no-startup-id aw-qt\n'
|
||||
} >> "$i3_config"
|
||||
} >>"$i3_config"
|
||||
fi
|
||||
|
||||
echo "✓ Added ActivityWatch to i3 config autostart"
|
||||
@ -274,7 +272,7 @@ create_i3blocks_status() {
|
||||
fi
|
||||
|
||||
# Create the status script
|
||||
cat > "$status_script" << 'EOF'
|
||||
cat >"$status_script" <<'EOF'
|
||||
#!/bin/bash
|
||||
# ActivityWatch status script for i3blocks
|
||||
# Shows ActivityWatch installation and running status
|
||||
@ -352,14 +350,14 @@ test_setup() {
|
||||
echo "=================="
|
||||
|
||||
echo "Installation status:"
|
||||
if check_activitywatch_installed > /dev/null 2>&1; then
|
||||
if check_activitywatch_installed >/dev/null 2>&1; then
|
||||
echo "✓ ActivityWatch is installed"
|
||||
else
|
||||
echo "✗ ActivityWatch is not installed"
|
||||
fi
|
||||
|
||||
echo "Running status:"
|
||||
if check_activitywatch_running > /dev/null 2>&1; then
|
||||
if check_activitywatch_running >/dev/null 2>&1; then
|
||||
echo "✓ ActivityWatch is running"
|
||||
else
|
||||
echo "✗ ActivityWatch is not running"
|
||||
|
||||
@ -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,35 +2,22 @@
|
||||
|
||||
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
|
||||
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
|
||||
if ! has_cmd pacman; then
|
||||
log_error "pacman not found. This script is intended for Arch Linux systems."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
@ -57,7 +44,7 @@ collect_kernel_headers() {
|
||||
local -a headers=()
|
||||
local kernel_pkg header_pkg
|
||||
for kernel_pkg in linux linux-lts linux-zen linux-hardened; do
|
||||
if pacman -Q "${kernel_pkg}" > /dev/null 2>&1; then
|
||||
if pacman -Q "${kernel_pkg}" >/dev/null 2>&1; then
|
||||
header_pkg="${kernel_pkg}-headers"
|
||||
headers+=("${header_pkg}")
|
||||
fi
|
||||
@ -72,7 +59,7 @@ maybe_remove_conflicting_host_packages() {
|
||||
local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts")
|
||||
local pkg
|
||||
for pkg in "${candidates[@]}"; do
|
||||
if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" > /dev/null 2>&1; then
|
||||
if [[ ${pkg} != "${selected_package}" ]] && pacman -Q "${pkg}" >/dev/null 2>&1; then
|
||||
log_warn "Removing conflicting package ${pkg} before installing ${selected_package}."
|
||||
pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}"
|
||||
fi
|
||||
@ -101,7 +88,7 @@ install_packages() {
|
||||
rebuild_virtualbox_modules() {
|
||||
local host_package=$1
|
||||
if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
|
||||
if command -v dkms > /dev/null 2>&1; then
|
||||
if command -v dkms >/dev/null 2>&1; then
|
||||
log_info "Rebuilding VirtualBox DKMS modules for all installed kernels."
|
||||
dkms autoinstall
|
||||
else
|
||||
@ -122,7 +109,7 @@ reload_virtualbox_modules() {
|
||||
local mod
|
||||
for mod in "${modules[@]}"; do
|
||||
if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then
|
||||
if ! modprobe "${mod}" > /dev/null 2>&1; then
|
||||
if ! modprobe "${mod}" >/dev/null 2>&1; then
|
||||
log_warn "Module ${mod} failed to load; check dmesg for details."
|
||||
fi
|
||||
fi
|
||||
@ -137,10 +124,10 @@ reload_virtualbox_modules() {
|
||||
warn_if_secure_boot_enabled() {
|
||||
local secure_boot_file
|
||||
if [[ -d /sys/firmware/efi/efivars ]]; then
|
||||
secure_boot_file=$(find /sys/firmware/efi/efivars -maxdepth 1 -name 'SecureBoot-*' -print -quit 2> /dev/null || true)
|
||||
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")
|
||||
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
|
||||
|
||||
@ -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
|
||||
@ -93,7 +88,7 @@ if [[ ! -d $ROOT_DIR ]]; then
|
||||
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; }
|
||||
@ -136,7 +131,7 @@ install_linters() {
|
||||
# checkbashisms may be in repos or AUR; try pacman first, then AUR helper
|
||||
if ! is_cmd checkbashisms; then
|
||||
if is_arch; then
|
||||
if ! sudo pacman -S --needed --noconfirm checkbashisms 2> /dev/null; then
|
||||
if ! sudo pacman -S --needed --noconfirm checkbashisms 2>/dev/null; then
|
||||
if have_aur_helper; then
|
||||
log_info "Installing checkbashisms from AUR (requires yay/paru)..."
|
||||
if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi
|
||||
@ -182,7 +177,7 @@ discover_shell_files() {
|
||||
local -a all
|
||||
all=()
|
||||
|
||||
if git -C "$base" rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||
if git -C "$base" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files -z)
|
||||
while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z)
|
||||
else
|
||||
@ -215,7 +210,7 @@ discover_shell_files() {
|
||||
|
||||
# Check shebang
|
||||
local first
|
||||
first=$(head -n 1 -- "$abs" 2> /dev/null || true)
|
||||
first=$(head -n 1 -- "$abs" 2>/dev/null || true)
|
||||
if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
|
||||
shells+=("$rel")
|
||||
continue
|
||||
@ -230,38 +225,38 @@ discover_shell_files() {
|
||||
done
|
||||
|
||||
# write lists
|
||||
: > "$REL_FILES_Z"
|
||||
: > "$ABS_FILES_Z"
|
||||
: >"$REL_FILES_Z"
|
||||
: >"$ABS_FILES_Z"
|
||||
for rel in "${shells[@]}"; do
|
||||
printf '%s\0' "$rel" >> "$REL_FILES_Z"
|
||||
printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z"
|
||||
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)
|
||||
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/^/ - /'
|
||||
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)
|
||||
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
|
||||
if ! shellcheck -x -S style "${FILES[@]}" >"$sc_out" 2>&1; then
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
else
|
||||
@ -271,7 +266,7 @@ run_linters() {
|
||||
log_info "Running shfmt (diff mode)..."
|
||||
local shfmt_out="$TMPDIR/shfmt.diff"
|
||||
if is_cmd shfmt; then
|
||||
if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" > "$shfmt_out" 2>&1; then
|
||||
if ! shfmt -d -i 2 -ci -sr -s "${FILES[@]}" >"$shfmt_out" 2>&1; then
|
||||
# shfmt returns non-zero when diff exists
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
@ -289,7 +284,7 @@ run_linters() {
|
||||
CBI_FILES=()
|
||||
for f in "${FILES[@]}"; do
|
||||
local first
|
||||
first=$(head -n 1 -- "$f" 2> /dev/null || true)
|
||||
first=$(head -n 1 -- "$f" 2>/dev/null || true)
|
||||
if [[ $first =~ bash || $first =~ zsh ]]; then
|
||||
continue
|
||||
fi
|
||||
@ -297,9 +292,9 @@ run_linters() {
|
||||
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
|
||||
checkbashisms "${CBI_FILES[@]}" >"$cbi_out" 2>&1
|
||||
else
|
||||
: > "$cbi_out"
|
||||
: >"$cbi_out"
|
||||
fi
|
||||
cbi_status=$?
|
||||
if [[ $cbi_status -eq 1 ]]; then
|
||||
@ -323,7 +318,7 @@ run_linters() {
|
||||
SH_FILES=()
|
||||
for f in "${FILES[@]}"; do
|
||||
local first
|
||||
first=$(head -n 1 -- "$f" 2> /dev/null || true)
|
||||
first=$(head -n 1 -- "$f" 2>/dev/null || true)
|
||||
if [[ $first =~ bash ]]; then
|
||||
BASH_FILES+=("$f")
|
||||
elif [[ $first =~ zsh ]]; then
|
||||
@ -334,23 +329,23 @@ run_linters() {
|
||||
done
|
||||
|
||||
if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then
|
||||
if ! bash -n "${BASH_FILES[@]}" 2> "$bash_out"; then
|
||||
if ! bash -n "${BASH_FILES[@]}" 2>"$bash_out"; then
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
fi
|
||||
if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then
|
||||
if ! zsh -n "${ZSH_FILES[@]}" 2> "$zsh_out"; then
|
||||
if ! zsh -n "${ZSH_FILES[@]}" 2>"$zsh_out"; then
|
||||
issues=$((issues + 1))
|
||||
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
|
||||
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
|
||||
if ! sh -n "${SH_FILES[@]}" 2>"$sh_out"; then
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -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:
|
||||
@ -75,7 +70,7 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
ensure_sudo() {
|
||||
if ! command -v sudo > /dev/null 2>&1; then
|
||||
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
|
||||
@ -94,7 +89,7 @@ install_policy() {
|
||||
log_info "Installing policy into: $target"
|
||||
sudo mkdir -p "$target"
|
||||
local policy_file="$target/unityhub-policy.json"
|
||||
sudo tee "$policy_file" > /dev/null << 'JSON'
|
||||
sudo tee "$policy_file" >/dev/null <<'JSON'
|
||||
{
|
||||
"AutoLaunchProtocolsFromOrigins": [
|
||||
{ "protocol": "unityhub", "origin": "https://id.unity.com", "allow": true },
|
||||
@ -116,7 +111,7 @@ JSON
|
||||
}
|
||||
|
||||
set_default_browser() {
|
||||
if command -v xdg-settings > /dev/null 2>&1; then
|
||||
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
|
||||
@ -127,7 +122,7 @@ set_default_browser() {
|
||||
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")"
|
||||
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
|
||||
@ -136,12 +131,12 @@ set_default_browser() {
|
||||
restart_thorium() {
|
||||
# Kill Thorium processes and start fresh
|
||||
log_info "Restarting Thorium..."
|
||||
pkill -9 -f 'thorium-browser' 2> /dev/null || true
|
||||
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
|
||||
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 &
|
||||
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."
|
||||
@ -152,7 +147,7 @@ main() {
|
||||
$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:
|
||||
@ -74,7 +69,7 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
require_cmd() {
|
||||
if ! command -v "$1" > /dev/null 2>&1; then
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@ -117,8 +112,8 @@ detect_unityhub() {
|
||||
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
|
||||
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"
|
||||
@ -127,7 +122,7 @@ detect_unityhub() {
|
||||
fi
|
||||
|
||||
# 2) Native binary in PATH
|
||||
if command -v unityhub > /dev/null 2>&1; then
|
||||
if command -v unityhub >/dev/null 2>&1; then
|
||||
local path
|
||||
path="$(command -v unityhub)"
|
||||
install_type="NATIVE"
|
||||
@ -150,8 +145,8 @@ detect_unityhub() {
|
||||
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
|
||||
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
|
||||
@ -203,7 +198,7 @@ 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
|
||||
cat >"$dest" <<DESK
|
||||
[Desktop Entry]
|
||||
Name=Unity Hub URL Handler
|
||||
Comment=Handle unityhub:// links for Unity Hub sign-in
|
||||
@ -223,14 +218,14 @@ DESK
|
||||
register_mime_handler() {
|
||||
local desktop_file="$1"
|
||||
# Update desktop database if available
|
||||
if command -v update-desktop-database > /dev/null 2>&1; then
|
||||
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
|
||||
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
|
||||
@ -243,8 +238,8 @@ register_mime_handler() {
|
||||
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)"
|
||||
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
|
||||
@ -257,8 +252,8 @@ verify_registration() {
|
||||
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
|
||||
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."
|
||||
@ -291,7 +286,7 @@ main() {
|
||||
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.
|
||||
|
||||
@ -11,15 +11,10 @@ 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
|
||||
@ -58,21 +53,31 @@ if ! has_cmd curl && ! has_cmd wget; then
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Priority 1: explicit URL
|
||||
if [[ -n ${RN_URL:-} ]]; then
|
||||
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
||||
# 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 "$RN_URL" -o "$tmp"
|
||||
curl -fsSL "$url" -o "$tmp" 2>/dev/null || true
|
||||
else
|
||||
wget -qO "$tmp" "$RN_URL"
|
||||
wget -qO "$tmp" "$url" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -s $tmp ]]; then
|
||||
mv "$tmp" "$dest"
|
||||
echo "Saved RNNoise model to: $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
|
||||
try_download_model "$RN_URL" "$dest"
|
||||
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
||||
fi
|
||||
|
||||
@ -85,26 +90,7 @@ NU_URLS=(
|
||||
"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)
|
||||
@ -112,26 +98,7 @@ RNNDN_URLS=(
|
||||
"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)
|
||||
@ -178,10 +145,10 @@ done
|
||||
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
|
||||
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)
|
||||
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
|
||||
|
||||
@ -7,20 +7,15 @@ 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] $*"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -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"
|
||||
@ -21,39 +26,12 @@ 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
|
||||
}
|
||||
|
||||
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
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
info "uv is already installed."
|
||||
return
|
||||
fi
|
||||
@ -64,21 +42,21 @@ install_uv() {
|
||||
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"
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
@ -142,14 +120,14 @@ configure_vscode_mcp() {
|
||||
|
||||
if [[ ! -f $mcp_config ]]; then
|
||||
info "Creating new VS Code MCP configuration at $mcp_config"
|
||||
echo '{}' > "$mcp_config"
|
||||
echo '{}' >"$mcp_config"
|
||||
else
|
||||
info "Updating existing VS Code MCP configuration at $mcp_config"
|
||||
fi
|
||||
|
||||
tmp="$(mktemp)"
|
||||
|
||||
if ! jq '.' "$mcp_config" > /dev/null 2>&1; then
|
||||
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
|
||||
@ -162,7 +140,7 @@ configure_vscode_mcp() {
|
||||
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."
|
||||
@ -173,13 +151,13 @@ verify_python_version() {
|
||||
require_command python "python"
|
||||
local version
|
||||
version="$(
|
||||
python - << 'PY'
|
||||
python - <<'PY'
|
||||
import sys
|
||||
print("%d.%d.%d" % sys.version_info[:3])
|
||||
PY
|
||||
)"
|
||||
local major minor
|
||||
IFS='.' read -r major minor _ <<< "$version"
|
||||
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
|
||||
@ -188,7 +166,7 @@ PY
|
||||
}
|
||||
|
||||
print_next_steps() {
|
||||
cat << 'EOT'
|
||||
cat <<'EOT'
|
||||
|
||||
Next steps:
|
||||
1. Launch Unity Hub and install a Unity Editor version 2021.3 LTS or newer.
|
||||
|
||||
@ -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:
|
||||
@ -30,13 +35,7 @@ EOF
|
||||
}
|
||||
|
||||
# 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
|
||||
@ -55,7 +54,7 @@ if [[ ! -f ${INPUT_IMAGE} ]]; then
|
||||
fi
|
||||
|
||||
# Validate resolution format (WIDTHxHEIGHT)
|
||||
if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then
|
||||
if ! validate_resolution "$RESOLUTION"; then
|
||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||
exit 1
|
||||
@ -63,19 +62,7 @@ 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
|
||||
|
||||
@ -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,10 +36,7 @@ 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() {
|
||||
|
||||
@ -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,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"
|
||||
|
||||
# Configuration -----------------------------------------------------------------
|
||||
TARGET_SESSION_NAME="Xfce Session"
|
||||
TARGET_PACKAGES=(
|
||||
@ -20,46 +25,19 @@ error() {
|
||||
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
|
||||
}
|
||||
|
||||
ensure_pacman() {
|
||||
require_command pacman "pacman"
|
||||
if ! grep -qi "arch" /etc/os-release 2> /dev/null; then
|
||||
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.
|
||||
@ -78,13 +56,13 @@ EOF
|
||||
logout_user() {
|
||||
local session_id="${XDG_SESSION_ID:-}"
|
||||
|
||||
if [[ -n $session_id ]] && loginctl show-session "$session_id" > /dev/null 2>&1; then
|
||||
if [[ -n $session_id ]] && loginctl show-session "$session_id" >/dev/null 2>&1; then
|
||||
info "Terminating current session (ID: $session_id) via loginctl."
|
||||
loginctl terminate-session "$session_id"
|
||||
return
|
||||
fi
|
||||
|
||||
if loginctl list-sessions 2> /dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then
|
||||
if loginctl list-sessions 2>/dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then
|
||||
info "Terminating all sessions for user '$USER' via loginctl."
|
||||
loginctl terminate-user "$USER"
|
||||
return
|
||||
|
||||
@ -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:
|
||||
@ -30,17 +35,7 @@ EOF
|
||||
}
|
||||
|
||||
# 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
|
||||
@ -59,7 +54,7 @@ if [[ ! -f ${INPUT_FILE} ]]; then
|
||||
fi
|
||||
|
||||
# Validate resolution format (WIDTHxHEIGHT)
|
||||
if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then
|
||||
if ! validate_resolution "$RESOLUTION"; then
|
||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||
exit 1
|
||||
@ -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}"
|
||||
@ -124,7 +119,7 @@ for ((i = 0; i < TOTAL_LINES; i += LINES_PER_IMAGE)); do
|
||||
# Create chunk file
|
||||
CHUNK_FILE="${TEMP_DIR}/chunk_${IMAGE_COUNT}.txt"
|
||||
for ((j = i; j < END_LINE; j++)); do
|
||||
echo "${LINES[$j]}" >> "${CHUNK_FILE}"
|
||||
echo "${LINES[$j]}" >>"${CHUNK_FILE}"
|
||||
done
|
||||
|
||||
# Determine output filename
|
||||
|
||||
@ -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