fix: format shell scripts with shfmt (convert tabs to 2 spaces)

Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-07 21:29:08 +00:00
parent 64b3116a5c
commit 2888c0b53d
66 changed files with 13480 additions and 13476 deletions

View File

@ -64,7 +64,7 @@ _build_aur_pkg() {
cd "$HOME/aur"
if [ ! -d "$pkg" ]; then git clone "$url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi
cd "$pkg"
rm -f -- *.pkg.tar.* 2>/dev/null || true
rm -f -- *.pkg.tar.* 2> /dev/null || true
yes | makepkg -s -c -C --noconfirm --needed
local built=(*.pkg.tar.zst)
yes | sudo pacman -U --noconfirm "${built[@]}"
@ -72,8 +72,8 @@ _build_aur_pkg() {
_install_repo_or_aur() {
local pkg="$1"
if pacman -Si "$pkg" >/dev/null 2>&1; then
if pacman -Qi "$pkg" >/dev/null 2>&1; then
if pacman -Si "$pkg" > /dev/null 2>&1; then
if pacman -Qi "$pkg" > /dev/null 2>&1; then
vlog "$pkg already installed"
else
yes | sudo pacman -Sy --noconfirm "$pkg"
@ -115,8 +115,8 @@ for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && b
if [ "$AMD_ENABLE_SI_CIK" = "1" ] || { [ "$AMD_ENABLE_SI_CIK" = "auto" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; }; then
info "Configuring amdgpu for SI/CIK (IS_SI=$IS_SI IS_CIK=$IS_CIK)"
TMP_CONF=$(mktemp)
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' >"$TMP_CONF"
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >>"$TMP_CONF"
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' > "$TMP_CONF"
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >> "$TMP_CONF"
sudo mkdir -p /etc/modprobe.d
sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf
rm -f "$TMP_CONF"
@ -142,7 +142,7 @@ else
fi
# Check active kernel driver
KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -44,10 +44,10 @@ fi
install_pkg() {
local pkg="$1"
if pacman -Qi "$pkg" >/dev/null 2>&1; then
if pacman -Qi "$pkg" > /dev/null 2>&1; then
vlog "$pkg already installed"
else
if pacman -Si "$pkg" >/dev/null 2>&1; then
if pacman -Si "$pkg" > /dev/null 2>&1; then
yes | sudo pacman -Sy --noconfirm "$pkg"
else
warn "Package $pkg not found in repos (not handling AUR here)"
@ -89,7 +89,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
else
info "Configuring enable_guc=$INTEL_ENABLE_GUC"
sudo mkdir -p /etc/modprobe.d
echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf >/dev/null
echo "options i915 enable_guc=$INTEL_ENABLE_GUC" | sudo tee /etc/modprobe.d/i915-guc.conf > /dev/null
if [ "$INTEL_SKIP_INITRAMFS" != 1 ] && [ -f /etc/mkinitcpio.conf ]; then
info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change"
sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually"
@ -100,7 +100,7 @@ if [ -n "$INTEL_ENABLE_GUC" ]; then
fi
# Report kernel driver
KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'i915|xe' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}"

View File

@ -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)
@ -80,17 +80,17 @@ 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
echo "$pkg_name" >> done.txt
else
echo "$pkg_name" >>failed.txt
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
@ -111,9 +111,9 @@ 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
try_aur_install "$repo_url" "$pkg_name"
@ -121,7 +121,7 @@ process_packages() {
else
try_aur_install "$repo_url" "$pkg_name"
fi
done <"$file_path"
done < "$file_path"
}
sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak
@ -132,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
@ -152,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
@ -175,14 +175,14 @@ 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
if ! pacman -Qi "$subpkg" &> /dev/null; then
return 1
fi
done
@ -196,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
@ -236,7 +236,7 @@ for pkg in "${pacman_packages[@]}"; do
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"
@ -247,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"
@ -259,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

View File

@ -7,20 +7,20 @@ 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
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
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
if command -v mountpoint > /dev/null 2>&1; then
while mountpoint -q "$TARGET"; do
umount -l "$TARGET" >/dev/null 2>&1 || break
umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i + 1))
((i > 20)) && break
done
@ -28,7 +28,7 @@ collapse_mounts() {
local cnt
cnt=$(mount_layers_count)
while ((cnt > 1)); do
umount -l "$TARGET" >/dev/null 2>&1 || break
umount -l "$TARGET" > /dev/null 2>&1 || break
i=$((i + 1))
((i > 20)) && break
cnt=$(mount_layers_count)
@ -40,9 +40,9 @@ collapse_mounts() {
stop_units_if_present() {
local units=(hosts-bind-mount.service hosts-guard.path)
for u in "${units[@]}"; do
if command -v systemctl >/dev/null 2>&1; then
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
systemctl stop "$u" >/dev/null 2>&1 || true
if command -v systemctl > /dev/null 2>&1; then
if systemctl list-unit-files 2> /dev/null | grep -q "^$u"; then
systemctl stop "$u" > /dev/null 2>&1 || true
fi
fi
done
@ -50,35 +50,35 @@ stop_units_if_present() {
# Remove immutable/append-only attributes
remove_host_attrs() {
if command -v lsattr >/dev/null 2>&1; then
if command -v lsattr > /dev/null 2>&1; then
local attrs
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
attrs=$(lsattr -d "$TARGET" 2> /dev/null || true)
if echo "$attrs" | grep -q " i "; then
chattr -i "$TARGET" >/dev/null 2>&1 || true
chattr -i "$TARGET" > /dev/null 2>&1 || true
fi
if echo "$attrs" | grep -q " a "; then
chattr -a "$TARGET" >/dev/null 2>&1 || true
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
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
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
if command -v systemctl > /dev/null 2>&1; then
systemctl start hosts-guard.path > /dev/null 2>&1 || true
fi
}
@ -87,5 +87,5 @@ 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
echo "$(date -Is) $phase-$state" >> /run/hosts-guard-hook.log 2> /dev/null || true
}

View File

@ -16,7 +16,7 @@ collapse_mounts
# Run enforcement script if available
if [[ -x $ENFORCE ]]; then
"$ENFORCE" >/dev/null 2>&1 || true
"$ENFORCE" > /dev/null 2>&1 || true
fi
# Apply protections

View File

@ -20,7 +20,7 @@ collapse_mounts
# Ensure writable by remounting if still read-only
if is_ro_mount; then
mount -o remount,rw "$TARGET" >/dev/null 2>&1 || collapse_mounts
mount -o remount,rw "$TARGET" > /dev/null 2>&1 || collapse_mounts
fi
log_hook "pre" "unlocking(done)"

View File

@ -69,9 +69,9 @@ load_saved_custom_entries() {
# Save current custom entries to state file
save_custom_entries_state() {
local entries="$1"
echo "$entries" | sort -u >"$CUSTOM_ENTRIES_STATE_FILE"
echo "$entries" | sort -u > "$CUSTOM_ENTRIES_STATE_FILE"
chmod 644 "$CUSTOM_ENTRIES_STATE_FILE"
chattr +i "$CUSTOM_ENTRIES_STATE_FILE" 2>/dev/null || true
chattr +i "$CUSTOM_ENTRIES_STATE_FILE" 2> /dev/null || true
}
# Helper function to count non-empty lines
@ -80,7 +80,7 @@ count_lines() {
if [[ -z $input ]]; then
echo 0
else
echo "$input" | grep -c . 2>/dev/null || echo 0
echo "$input" | grep -c . 2> /dev/null || echo 0
fi
}
@ -144,7 +144,7 @@ check_custom_entries_protection() {
echo "You are attempting to REMOVE the following blocked entries:"
while IFS= read -r entry; do
echo " - $entry"
done <<<"$removed_entries"
done <<< "$removed_entries"
echo ""
echo "This is NOT allowed. The only way to unblock sites is to:"
echo ""
@ -168,7 +168,7 @@ fi
sudo systemctl enable systemd-resolved
# Remove all attributes from /etc/hosts to allow modifications
sudo chattr -i -a /etc/hosts 2>/dev/null || true
sudo chattr -i -a /etc/hosts 2> /dev/null || true
# Source and local cache configuration
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
@ -180,9 +180,9 @@ extract_date_epoch_from_file() {
# Grep "# Date:" line and convert to epoch seconds (UTC)
local f="$1"
local line
line=$(grep -m1 '^# Date:' "$f" 2>/dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/')
line=$(grep -m1 '^# Date:' "$f" 2> /dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/')
if [[ -n $line ]]; then
date -u -d "$line" +%s 2>/dev/null || echo ""
date -u -d "$line" +%s 2> /dev/null || echo ""
else
echo ""
fi
@ -195,7 +195,7 @@ fetch_remote_header() {
return 0
fi
# Fallback may download more, but we only keep first lines
if curl -LfsS --max-time 10 "$URL" | head -n 20 >"$out"; then
if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then
return 0
fi
return 1
@ -272,7 +272,7 @@ sudo sed -i 's/^0\.0\.0\.0 messenger\.com/#0.0.0.0 messenger.com/' /etc/hosts
# Add custom entries for YouTube and Discord
echo "Adding custom entries for YouTube and Discord..."
tee -a /etc/hosts >/dev/null <<'EOF'
tee -a /etc/hosts > /dev/null << 'EOF'
# Custom blocking entries
# YouTube
@ -407,7 +407,7 @@ echo "Saving custom entries state for protection mechanism..."
script_path="$(readlink -f "$0")"
current_custom_entries=$(extract_custom_entries_from_script "$script_path")
# Remove immutable from state file if it exists
chattr -i "$CUSTOM_ENTRIES_STATE_FILE" 2>/dev/null || true
chattr -i "$CUSTOM_ENTRIES_STATE_FILE" 2> /dev/null || true
save_custom_entries_state "$current_custom_entries"
echo "✅ Custom entries state saved to $CUSTOM_ENTRIES_STATE_FILE"

View File

@ -15,23 +15,23 @@ show_error() {
}
# Validate and load config file
if [[ ! -f "$SHUTDOWN_CONFIG" ]]; then
if [[ ! -f $SHUTDOWN_CONFIG ]]; then
show_error "NO CONFIG"
fi
# Source the config file to get MON_WED_HOUR and THU_SUN_HOUR
# shellcheck source=/dev/null
if ! source "$SHUTDOWN_CONFIG" 2>/dev/null; then
if ! source "$SHUTDOWN_CONFIG" 2> /dev/null; then
show_error "BAD CONFIG"
fi
# Validate that required variables are set
if [[ -z "${MON_WED_HOUR:-}" ]] || [[ -z "${THU_SUN_HOUR:-}" ]]; then
if [[ -z ${MON_WED_HOUR:-} ]] || [[ -z ${THU_SUN_HOUR:-} ]]; then
show_error "MISSING VARS"
fi
# Validate that values are numbers
if ! [[ "$MON_WED_HOUR" =~ ^[0-9]+$ ]] || ! [[ "$THU_SUN_HOUR" =~ ^[0-9]+$ ]]; then
if ! [[ $MON_WED_HOUR =~ ^[0-9]+$ ]] || ! [[ $THU_SUN_HOUR =~ ^[0-9]+$ ]]; then
show_error "INVALID HOURS"
fi

View File

@ -65,7 +65,7 @@ require_root() {
}
usage() {
cat <<'EOF'
cat << 'EOF'
Check and Enable Digital Wellbeing Services
============================================
@ -150,7 +150,7 @@ report_and_fix() {
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
if [[ $DRY_RUN -eq 0 && -n $verify_service ]] && systemctl is-enabled "$verify_service" &> /dev/null; then
_status="ok"
fi
else
@ -244,14 +244,14 @@ check_midnight_shutdown() {
local issues=()
# Check timer
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
msg "day-specific-shutdown.timer is enabled"
else
issues+=("day-specific-shutdown.timer is not enabled")
status="error"
fi
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
msg "day-specific-shutdown.timer is active"
else
issues+=("day-specific-shutdown.timer is not active")
@ -288,14 +288,14 @@ check_startup_monitor() {
local issues=()
# Check timer (the timer triggers the service, so we check the timer)
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then
msg "pc-startup-monitor.timer is enabled"
else
issues+=("pc-startup-monitor.timer is not enabled")
status="error"
fi
if systemctl is-active pc-startup-monitor.timer &>/dev/null; then
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
msg "pc-startup-monitor.timer is active"
else
issues+=("pc-startup-monitor.timer is not active")
@ -331,14 +331,14 @@ check_periodic_systems() {
local issues=()
# Check timer
if systemctl is-enabled periodic-system-maintenance.timer &>/dev/null; then
if systemctl is-enabled periodic-system-maintenance.timer &> /dev/null; then
msg "periodic-system-maintenance.timer is enabled"
else
issues+=("periodic-system-maintenance.timer is not enabled")
status="error"
fi
if systemctl is-active periodic-system-maintenance.timer &>/dev/null; then
if systemctl is-active periodic-system-maintenance.timer &> /dev/null; then
msg "periodic-system-maintenance.timer is active"
else
issues+=("periodic-system-maintenance.timer is not active")
@ -346,7 +346,7 @@ check_periodic_systems() {
fi
# Check startup service
if systemctl is-enabled periodic-system-startup.service &>/dev/null; then
if systemctl is-enabled periodic-system-startup.service &> /dev/null; then
msg "periodic-system-startup.service is enabled"
else
issues+=("periodic-system-startup.service is not enabled")
@ -354,14 +354,14 @@ check_periodic_systems() {
fi
# Check hosts file monitor
if systemctl is-enabled hosts-file-monitor.service &>/dev/null; then
if systemctl is-enabled hosts-file-monitor.service &> /dev/null; then
msg "hosts-file-monitor.service is enabled"
else
issues+=("hosts-file-monitor.service is not enabled")
status="error"
fi
if systemctl is-active hosts-file-monitor.service &>/dev/null; then
if systemctl is-active hosts-file-monitor.service &> /dev/null; then
msg "hosts-file-monitor.service is active"
else
issues+=("hosts-file-monitor.service is not active")
@ -391,7 +391,7 @@ check_hosts() {
# Check /etc/hosts exists and has content
if [[ -f /etc/hosts ]]; then
local line_count
line_count=$(wc -l </etc/hosts)
line_count=$(wc -l < /etc/hosts)
if [[ $line_count -gt 100 ]]; then
msg "/etc/hosts exists with $line_count lines (StevenBlack list likely installed)"
else
@ -405,7 +405,7 @@ check_hosts() {
# Check if hosts file is immutable
local attrs
attrs=$(lsattr /etc/hosts 2>/dev/null | cut -d' ' -f1 || echo "")
attrs=$(lsattr /etc/hosts 2> /dev/null | cut -d' ' -f1 || echo "")
if [[ $attrs == *"i"* ]]; then
msg "/etc/hosts has immutable attribute set"
else
@ -422,14 +422,14 @@ check_hosts() {
fi
# Check hosts guard path watcher
if systemctl is-enabled hosts-guard.path &>/dev/null; then
if systemctl is-enabled hosts-guard.path &> /dev/null; then
msg "hosts-guard.path is enabled"
else
issues+=("hosts-guard.path is not enabled")
status="error"
fi
if systemctl is-active hosts-guard.path &>/dev/null; then
if systemctl is-active hosts-guard.path &> /dev/null; then
msg "hosts-guard.path is active"
else
issues+=("hosts-guard.path is not active")
@ -437,7 +437,7 @@ check_hosts() {
fi
# Check hosts bind mount service
if systemctl is-enabled hosts-bind-mount.service &>/dev/null; then
if systemctl is-enabled hosts-bind-mount.service &> /dev/null; then
msg "hosts-bind-mount.service is enabled"
else
issues+=("hosts-bind-mount.service is not enabled")
@ -489,7 +489,7 @@ check_hosts() {
if [[ $STATUS_ONLY -eq 0 ]]; then
# Run hosts install first
if [[ ! -f /etc/hosts ]] || [[ $(wc -l </etc/hosts) -lt 100 ]]; then
if [[ ! -f /etc/hosts ]] || [[ $(wc -l < /etc/hosts) -lt 100 ]]; then
note "Installing hosts file..."
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
run bash "$HOSTS_INSTALL_SCRIPT"
@ -500,7 +500,7 @@ check_hosts() {
fi
# Run hosts guard setup
if ! systemctl is-enabled hosts-guard.path &>/dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then
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
run bash "$HOSTS_GUARD_SCRIPT"
@ -523,7 +523,7 @@ check_hosts() {
# Re-verify after fixes
if [[ $DRY_RUN -eq 0 ]]; then
if systemctl is-enabled hosts-guard.path &>/dev/null &&
if systemctl is-enabled hosts-guard.path &> /dev/null &&
[[ -f /usr/local/sbin/enforce-hosts.sh ]] &&
[[ -f /usr/local/share/locked-hosts ]] &&
[[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then

View File

@ -17,8 +17,8 @@ notify() {
local urgency="${3:-normal}"
local timeout="${4:-5000}"
if command -v notify-send &>/dev/null; then
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true
if command -v notify-send &> /dev/null; then
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2> /dev/null || true
fi
}
@ -44,7 +44,7 @@ declare -A REAL_BINARIES=(
# Ensure state directory exists
ensure_state_dir() {
mkdir -p "$STATE_DIR" 2>/dev/null || true
mkdir -p "$STATE_DIR" 2> /dev/null || true
}
# Log message with timestamp
@ -52,7 +52,7 @@ log_message() {
local msg
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$msg" >&2
echo "$msg" >>"$LOG_FILE" 2>/dev/null || true
echo "$msg" >> "$LOG_FILE" 2> /dev/null || true
}
# Get current hour key (YYYY-MM-DD-HH format)
@ -76,7 +76,7 @@ was_opened_this_hour() {
if [[ -f $state_file ]]; then
local last_hour
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
last_hour=$(cat "$state_file" 2> /dev/null || echo "")
if [[ $last_hour == "$current_hour" ]]; then
return 0 # Was opened this hour
fi
@ -92,7 +92,7 @@ record_opening() {
local current_hour
current_hour=$(get_hour_key)
echo "$current_hour" >"$state_file"
echo "$current_hour" > "$state_file"
log_message "ALLOWED: $app opened (first time this hour: $current_hour)"
}
@ -179,7 +179,7 @@ install_wrapper() {
link_target=$(readlink "$wrapper_path")
echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig"
# Remove symlink and create .orig that stores the link target info
echo "SYMLINK:$link_target" >"${wrapper_path}.orig"
echo "SYMLINK:$link_target" > "${wrapper_path}.orig"
rm "$wrapper_path"
else
echo " Backing up $wrapper_path -> ${wrapper_path}.orig"
@ -187,7 +187,7 @@ install_wrapper() {
fi
echo " Creating wrapper at $wrapper_path"
cat >"$wrapper_path" <<WRAPPER_EOF
cat > "$wrapper_path" << WRAPPER_EOF
#!/bin/bash
# Auto-generated wrapper for $app - blocks compulsive opening
# Real binary: $real_binary
@ -214,7 +214,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 "")
orig_content=$(cat "${wrapper_path}.orig" 2> /dev/null || echo "")
if [[ $orig_content == SYMLINK:* ]]; then
local link_target="${orig_content#SYMLINK:}"
echo " Restoring symlink $wrapper_path -> $link_target"
@ -276,7 +276,7 @@ install_pacman_hook() {
mkdir -p "$hook_dir"
cat >"$hook_file" <<'HOOK_EOF'
cat > "$hook_file" << 'HOOK_EOF'
[Trigger]
Operation = Upgrade
Operation = Install
@ -299,7 +299,7 @@ HOOK_EOF
# Uninstall pacman hook
uninstall_pacman_hook() {
local hook_file="/etc/pacman.d/hooks/95-compulsive-block-rewrap.hook"
if [[ -f "$hook_file" ]]; then
if [[ -f $hook_file ]]; then
rm -f "$hook_file"
echo "✓ Pacman hook removed"
fi
@ -313,7 +313,7 @@ rewrap_quiet() {
local wrapper_path="${APPS[$app]}"
# Check if wrapper was overwritten (no longer our wrapper script)
if [[ -f "$wrapper_path" ]] && ! grep -q "block-compulsive-opening" "$wrapper_path" 2>/dev/null; then
if [[ -f $wrapper_path ]] && ! grep -q "block-compulsive-opening" "$wrapper_path" 2> /dev/null; then
# Wrapper was overwritten by package update
log_message "REWRAP: $app wrapper was overwritten, re-installing"
@ -321,7 +321,7 @@ rewrap_quiet() {
rm -f "${wrapper_path}.orig"
# Re-install wrapper
install_wrapper "$app" >>"$LOG_FILE" 2>&1 || true
install_wrapper "$app" >> "$LOG_FILE" 2>&1 || true
fi
done
@ -365,7 +365,7 @@ show_status() {
if [[ -f $state_file ]]; then
local last_hour
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
last_hour=$(cat "$state_file" 2> /dev/null || echo "")
if [[ $last_hour == "$current_hour" ]]; then
status="already opened (blocked until next hour)"
icon="●"
@ -415,7 +415,7 @@ reset_all() {
# Show usage
show_usage() {
cat <<EOF
cat << EOF
Block Compulsive Opening Script
================================

View File

@ -15,14 +15,14 @@ warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; }
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
if ! command -v "$1" > /dev/null 2>&1; then
err "Missing dependency: $1"
MISSING=1
fi
}
usage() {
cat <<EOF
cat << EOF
${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub
Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox]
@ -76,7 +76,7 @@ require_cmd tar
require_cmd find
require_cmd sed
require_cmd awk
if ! command -v jq >/dev/null 2>&1; then
if ! command -v jq > /dev/null 2>&1; then
warn "jq not found — will fall back to a simpler tag detection method."
fi
[[ $MISSING -eq 1 ]] && {
@ -89,7 +89,7 @@ REPO_NAME="LeechBlockNG"
get_latest_tag() {
local tag
if command -v jq >/dev/null 2>&1; then
if command -v jq > /dev/null 2>&1; then
tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | jq -r '.tag_name // empty' || true)
if [[ -n $tag && $tag != "null" ]]; then
echo "$tag"
@ -157,7 +157,7 @@ else
info "Installing to $VERSION_DIR"
mkdir -p "$VERSION_DIR"
# Copy the extension directory as-is (avoid bringing tests or build scripts)
rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2>/dev/null || cp -a "$ext_dir/." "$VERSION_DIR/"
rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2> /dev/null || cp -a "$ext_dir/." "$VERSION_DIR/"
ln -sfn "$VERSION_DIR" "$CURRENT_LINK"
fi
@ -203,7 +203,7 @@ create_wrapper_and_desktop() {
real_bin=$(command -v "$bin" || true)
[[ -z $real_bin ]] && return
cat >"$wrapper" <<WRAP
cat > "$wrapper" << WRAP
#!/usr/bin/env bash
exec "$real_bin" --load-extension="$EXT_PATH" "$@"
WRAP
@ -211,7 +211,7 @@ WRAP
# Try to reuse icon from an existing desktop file if available
local sys_desktop existing_icon existing_name categories
sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2>/dev/null | head -n1 || true)
sys_desktop=$(grep -RIl "^Exec=.*${bin}" /usr/share/applications 2> /dev/null | head -n1 || true)
if [[ -n $sys_desktop ]]; then
existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true)
existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true)
@ -222,7 +222,7 @@ WRAP
[[ -z $categories ]] && categories="Network;WebBrowser;"
local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop"
cat >"$desktop_file" <<DESK
cat > "$desktop_file" << DESK
[Desktop Entry]
Name=${existing_name} (LeechBlock)
Exec=${wrapper} %U
@ -240,14 +240,14 @@ DESK
info "Detecting installed browsers…"
for bin in "${!BROWSERS[@]}"; do
if command -v "$bin" >/dev/null 2>&1; then
if command -v "$bin" > /dev/null 2>&1; then
create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}"
fi
done
ff_found=0
for bin in "${!FIREFOXES[@]}"; do
if command -v "$bin" >/dev/null 2>&1; then
if command -v "$bin" > /dev/null 2>&1; then
ff_found=1
fi
done
@ -261,7 +261,7 @@ fi
if [[ $ff_found -eq 1 ]]; then
echo
warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing."
cat <<FF
cat << FF
Options:
1) Install from Mozilla Add-ons (recommended):
https://addons.mozilla.org/firefox/addon/leechblock-ng/
@ -294,13 +294,13 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
# Determine policy directories for detected Firefox-like browsers
declare -a POLICY_DIRS
POLICY_DIRS=()
if command -v firefox >/dev/null 2>&1; then
if command -v firefox > /dev/null 2>&1; then
POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution")
fi
if command -v firefox-developer-edition >/dev/null 2>&1; then
if command -v firefox-developer-edition > /dev/null 2>&1; then
POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution")
fi
if command -v librewolf >/dev/null 2>&1; then
if command -v librewolf > /dev/null 2>&1; then
POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution")
fi
# Generic mozilla path as fallback
@ -313,7 +313,7 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
if sudo test -f "$existing"; then
info "Merging into existing policies.json at $existing"
sudo cp "$existing" "$tmp_pol"
if command -v jq >/dev/null 2>&1; then
if command -v jq > /dev/null 2>&1; then
merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" '
.policies |= (. // {}) |
.policies.ExtensionSettings |= (. // {}) |
@ -323,7 +323,7 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
.policies.ExtensionSettings[$id].install_url = $url
' "$tmp_pol") || merged=""
if [[ -n $merged ]]; then
printf '%s\n' "$merged" >"$tmp_pol"
printf '%s\n' "$merged" > "$tmp_pol"
else
warn "jq merge failed; skipping $pol_target"
rm -f "$tmp_pol"
@ -332,7 +332,7 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
else
warn "jq not available; creating minimal policies.json (existing file will be backed up)."
sudo cp "$existing" "${existing}.bak.$(date +%s)"
cat >"$tmp_pol" <<JSON
cat > "$tmp_pol" << JSON
{
"policies": {
"ExtensionSettings": {
@ -348,7 +348,7 @@ JSON
fi
else
info "Creating new policies.json at $pol_target"
cat >"$tmp_pol" <<JSON
cat > "$tmp_pol" << JSON
{
"policies": {
"ExtensionSettings": {

View File

@ -16,7 +16,7 @@ source "$SCRIPT_DIR/../lib/common.sh"
# Configuration
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism"
mkdir -p "$LOG_DIR" 2>/dev/null || true
mkdir -p "$LOG_DIR" 2> /dev/null || true
export LOG_FILE="$LOG_DIR/music-parallelism.log"
CHECK_INTERVAL=3
@ -76,14 +76,14 @@ find_music_services() {
for service in "${MUSIC_SERVICES[@]}"; do
# Check for browser tabs with music services
# This checks window titles which usually contain the URL or tab title
if command -v xdotool &>/dev/null; then
if xdotool search --name "$service" &>/dev/null 2>&1; then
if command -v xdotool &> /dev/null; then
if xdotool search --name "$service" &> /dev/null 2>&1; then
found_services+=("$service (window)")
fi
fi
# Check for dedicated desktop apps
if pgrep -i -f "$service" &>/dev/null; then
if pgrep -i -f "$service" &> /dev/null; then
found_services+=("$service (process)")
fi
done
@ -102,19 +102,19 @@ kill_music_services() {
# Kill YouTube Music browser tabs
# YouTube Music runs in browser, so we need to close specific tabs
# We use xdotool to find and close windows with "YouTube Music" or "music.youtube.com"
if command -v xdotool &>/dev/null; then
if command -v xdotool &> /dev/null; then
# Find windows with YouTube Music in title
local yt_music_windows
yt_music_windows=$(xdotool search --name "YouTube Music" 2>/dev/null || true)
yt_music_windows=$(xdotool search --name "YouTube Music" 2> /dev/null || true)
for wid in $yt_music_windows; do
if [[ -n $wid ]]; then
# Get window name for logging
local wname
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
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
log_message "Closing YouTube Music window: $wname (ID: $wid)"
xdotool windowclose "$wid" 2>/dev/null || true
xdotool windowclose "$wid" 2> /dev/null || true
killed=true
fi
fi
@ -122,41 +122,41 @@ kill_music_services() {
fi
# Kill YouTube Music Electron app
if pgrep -f "youtube-music" &>/dev/null; then
if pgrep -f "youtube-music" &> /dev/null; then
log_message "Killing YouTube Music app"
pkill -9 -f "youtube-music" 2>/dev/null || true
pkill -9 -f "youtube-music" 2> /dev/null || true
killed=true
fi
# Kill Spotify
if pgrep -x "spotify" &>/dev/null; then
if pgrep -x "spotify" &> /dev/null; then
log_message "Killing Spotify"
pkill -9 -x "spotify" 2>/dev/null || true
pkill -9 -x "spotify" 2> /dev/null || true
killed=true
fi
# Kill other music streaming app processes
local music_processes=("tidal" "deezer" "Amazon Music")
for proc in "${music_processes[@]}"; do
if pgrep -i -f "$proc" &>/dev/null; then
if pgrep -i -f "$proc" &> /dev/null; then
log_message "Killing $proc"
pkill -9 -i -f "$proc" 2>/dev/null || true
pkill -9 -i -f "$proc" 2> /dev/null || true
killed=true
fi
done
# Close browser tabs for web-based music services
if command -v xdotool &>/dev/null; then
if command -v xdotool &> /dev/null; then
local web_music_patterns=("music.apple.com" "soundcloud.com" "pandora.com" "deezer.com" "tidal.com")
for pattern in "${web_music_patterns[@]}"; do
local windows
windows=$(xdotool search --name "$pattern" 2>/dev/null || true)
windows=$(xdotool search --name "$pattern" 2> /dev/null || true)
for wid in $windows; do
if [[ -n $wid ]]; then
local wname
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
wname=$(xdotool getwindowname "$wid" 2> /dev/null || echo "unknown")
log_message "Closing music service window: $wname (ID: $wid)"
xdotool windowclose "$wid" 2>/dev/null || true
xdotool windowclose "$wid" 2> /dev/null || true
killed=true
fi
done
@ -175,8 +175,8 @@ notify_user() {
local message="Music stopped - focus mode active ($focus_app detected)"
# Try to send desktop notification
if command -v notify-send &>/dev/null; then
notify-send -u normal -t 5000 "🎵 Music Parallelism" "$message" 2>/dev/null || true
if command -v notify-send &> /dev/null; then
notify-send -u normal -t 5000 "🎵 Music Parallelism" "$message" 2> /dev/null || true
fi
log_message "$message"
@ -192,16 +192,16 @@ instant_monitor_loop() {
while true; do
# Only check if focus app is running
if is_focus_app_running &>/dev/null; then
if is_focus_app_running &> /dev/null; then
# Instant kill youtube-music if detected
if pgrep -f "youtube-music" &>/dev/null; then
pkill -9 -f "youtube-music" 2>/dev/null || true
if pgrep -f "youtube-music" &> /dev/null; then
pkill -9 -f "youtube-music" 2> /dev/null || true
log_message "INSTANT KILL: YouTube Music terminated"
notify-send -u normal -t 2000 "🎵 YouTube Music killed" "Focus mode active" 2>/dev/null || true
notify-send -u normal -t 2000 "🎵 YouTube Music killed" "Focus mode active" 2> /dev/null || true
fi
# Also check other music services
if pgrep -x "spotify" &>/dev/null; then
pkill -9 -x "spotify" 2>/dev/null || true
if pgrep -x "spotify" &> /dev/null; then
pkill -9 -x "spotify" 2> /dev/null || true
log_message "INSTANT KILL: Spotify terminated"
fi
fi
@ -248,9 +248,9 @@ show_status() {
local focus_running=false
# Check windows
if command -v xdotool &>/dev/null; then
if command -v xdotool &> /dev/null; then
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
if xdotool search --name "$app" &>/dev/null 2>&1; then
if xdotool search --name "$app" &> /dev/null 2>&1; then
echo "$app (WINDOW OPEN)"
focus_running=true
fi
@ -259,7 +259,7 @@ show_status() {
# Check processes
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
if pgrep -f "$app" &>/dev/null; then
if pgrep -f "$app" &> /dev/null; then
echo "$app (PROCESS RUNNING)"
focus_running=true
fi
@ -272,7 +272,7 @@ show_status() {
echo ""
echo "Music Services:"
local music_running=false
if music_services=$(find_music_services 2>/dev/null); then
if music_services=$(find_music_services 2> /dev/null); then
echo "$music_services" | while read -r svc; do
echo "$svc (RUNNING)"
done
@ -320,16 +320,16 @@ show_usage() {
# Main
case "${1:-instant}" in
monitor | start | run)
monitor | start | run)
monitor_loop
;;
instant | fast)
instant | fast)
instant_monitor_loop
;;
status)
status)
show_status
;;
kill)
kill)
log_message "Manual kill requested"
if kill_music_services; then
echo "Music services killed"
@ -337,10 +337,10 @@ kill)
echo "No music services found to kill"
fi
;;
help | -h | --help)
help | -h | --help)
show_usage
;;
*)
*)
echo "Unknown command: $1"
show_usage
exit 1

View File

@ -64,7 +64,7 @@ else
echo -e "${YELLOW}Warning:${NC} Missing greylist source at ${GREYLIST_SOURCE}${NC}"
fi
chmod +x "$WRAPPER_DEST"
chmod 644 "$WORDS_DEST" "$BLOCKED_DEST" "$WHITELIST_DEST" "$GREYLIST_DEST" 2>/dev/null || true
chmod 644 "$WORDS_DEST" "$BLOCKED_DEST" "$WHITELIST_DEST" "$GREYLIST_DEST" 2> /dev/null || true
# Automatically use symbolic link installation method
echo -e "${YELLOW}Installing using symbolic link method...${NC}"

View File

@ -96,7 +96,7 @@ post_relock_hosts() {
# Ensure periodic system services (timer/monitor) are set up; if not, trigger setup
ensure_periodic_maintenance() {
# Only proceed if systemd/systemctl is available
if ! command -v systemctl >/dev/null 2>&1; then
if ! command -v systemctl > /dev/null 2>&1; then
return 0
fi
@ -235,10 +235,10 @@ function has_noconfirm_flag() {
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)
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
@ -268,8 +268,8 @@ check_and_handle_db_lock() {
local gui_holder=0
for pid in "${holders[@]}"; do
local comm args lower
comm=$(ps -p "$pid" -o comm= 2>/dev/null || true)
args=$(ps -p "$pid" -o args= 2>/dev/null || true)
comm=$(ps -p "$pid" -o comm= 2> /dev/null || true)
args=$(ps -p "$pid" -o args= 2> /dev/null || true)
lower="${comm,,} ${args,,}"
if [[ $lower == *" pacman"* || $lower == pacman* || $lower == *"/pacman "* || $lower == *" pamac"* ]]; then
pac_holder=1
@ -285,13 +285,13 @@ check_and_handle_db_lock() {
if [[ $gui_holder -eq 1 ]]; then
echo -e "${YELLOW}A background software updater is holding the pacman lock. Attempting to stop it...${NC}" >&2
if command -v systemctl >/dev/null 2>&1; then
systemctl --quiet stop packagekit.service 2>/dev/null || true
systemctl --quiet stop packagekit 2>/dev/null || true
if command -v systemctl > /dev/null 2>&1; then
systemctl --quiet stop packagekit.service 2> /dev/null || true
systemctl --quiet stop packagekit 2> /dev/null || true
fi
pkill -x packagekitd 2>/dev/null || true
pkill -f gnome-software 2>/dev/null || true
pkill -f discover 2>/dev/null || true
pkill -x packagekitd 2> /dev/null || true
pkill -f gnome-software 2> /dev/null || true
pkill -f discover 2> /dev/null || true
sleep 1
# Re-check holders
@ -315,7 +315,7 @@ check_and_handle_db_lock() {
# Decide whether to remove the lock
local now epoch age
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
if epoch=$(stat -c %Y "$lock_file" 2> /dev/null); then
now=$(date +%s)
age=$((now - epoch))
else
@ -347,7 +347,7 @@ function remove_installed_packages_matching() {
local check_function="$1"
local label="$2"
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2> /dev/null)
local to_remove=()
for name in "${installed_names[@]}"; do
if "$check_function" "$name"; then
@ -530,8 +530,8 @@ function run_word_challenge() {
read -t "$timeout_seconds" -r user_input
read_status=$?
kill "$display_pid" 2>/dev/null
wait "$display_pid" 2>/dev/null
kill "$display_pid" 2> /dev/null
wait "$display_pid" 2> /dev/null
echo
if [[ $read_status -ne 0 ]]; then

View File

@ -32,20 +32,20 @@ CANONICAL_CONFIG="/usr/local/share/locked-shutdown-schedule.conf"
# Check if trying to make schedule more lenient (later shutdown / earlier morning end)
check_schedule_protection() {
# Skip check if no canonical config exists (first install)
if [[ ! -f "$CANONICAL_CONFIG" ]]; then
if [[ ! -f $CANONICAL_CONFIG ]]; then
return 0
fi
# Load canonical values
local canonical_mon_wed canonical_thu_sun canonical_morning_end
# shellcheck source=/dev/null
source "$CANONICAL_CONFIG" 2>/dev/null || return 0
source "$CANONICAL_CONFIG" 2> /dev/null || return 0
canonical_mon_wed="${MON_WED_HOUR:-}"
canonical_thu_sun="${THU_SUN_HOUR:-}"
canonical_morning_end="${MORNING_END_HOUR:-}"
# If canonical values are empty, skip check
if [[ -z "$canonical_mon_wed" ]] || [[ -z "$canonical_thu_sun" ]] || [[ -z "$canonical_morning_end" ]]; then
if [[ -z $canonical_mon_wed ]] || [[ -z $canonical_thu_sun ]] || [[ -z $canonical_morning_end ]]; then
return 0
fi
@ -189,13 +189,13 @@ show_current_status() {
# Check systemd status
if $timer_exists; then
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is enabled"
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is active"
echo ""
echo "Next scheduled shutdown check:"
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | grep day-specific-shutdown || echo "Timer information not available"
systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | grep day-specific-shutdown || echo "Timer information not available"
else
echo "✗ Timer is not active"
fi
@ -210,9 +210,9 @@ show_current_status() {
# Check monitor service status
echo "Monitor Service Status:"
if systemctl is-enabled shutdown-timer-monitor.service &>/dev/null; then
if systemctl is-enabled shutdown-timer-monitor.service &> /dev/null; then
echo "✓ Monitor is enabled"
if systemctl is-active shutdown-timer-monitor.service &>/dev/null; then
if systemctl is-active shutdown-timer-monitor.service &> /dev/null; then
echo "✓ Monitor is active (will re-enable timer if disabled)"
else
echo "✗ Monitor is not active"
@ -228,10 +228,10 @@ show_current_status() {
local config_file="/etc/shutdown-schedule.conf"
local canonical_file="/usr/local/share/locked-shutdown-schedule.conf"
if [[ -f "$config_file" ]]; then
if [[ -f $config_file ]]; then
echo "✓ Config file exists"
# Check immutable attribute
if lsattr "$config_file" 2>/dev/null | grep -q '^....i'; then
if lsattr "$config_file" 2> /dev/null | grep -q '^....i'; then
echo "✓ Config file is immutable (chattr +i)"
else
echo "✗ Config file is NOT immutable"
@ -240,15 +240,15 @@ show_current_status() {
echo "✗ Config file missing"
fi
if [[ -f "$canonical_file" ]]; then
if [[ -f $canonical_file ]]; then
echo "✓ Canonical copy exists"
else
echo "✗ Canonical copy missing"
fi
if systemctl is-enabled shutdown-schedule-guard.path &>/dev/null; then
if systemctl is-enabled shutdown-schedule-guard.path &> /dev/null; then
echo "✓ Config path watcher is enabled"
if systemctl is-active shutdown-schedule-guard.path &>/dev/null; then
if systemctl is-active shutdown-schedule-guard.path &> /dev/null; then
echo "✓ Config path watcher is active"
else
echo "✗ Config path watcher is not active"
@ -290,10 +290,10 @@ create_shutdown_config() {
local canonical_file="/usr/local/share/locked-shutdown-schedule.conf"
# Remove immutable attribute if it exists (to allow update)
chattr -i "$config_file" 2>/dev/null || true
chattr -i "$canonical_file" 2>/dev/null || true
chattr -i "$config_file" 2> /dev/null || true
chattr -i "$canonical_file" 2> /dev/null || true
cat >"$config_file" <<EOF
cat > "$config_file" << EOF
# Shutdown schedule configuration
# This file is managed by setup_midnight_shutdown.sh
# Used by: day-specific-shutdown-check.sh, shutdown_countdown.sh (i3blocks)
@ -344,7 +344,7 @@ create_config_guard() {
local guard_path="/etc/systemd/system/shutdown-schedule-guard.path"
# Create enforcement script
cat >"$enforce_script" <<'EOF'
cat > "$enforce_script" << 'EOF'
#!/bin/bash
# Enforce canonical /etc/shutdown-schedule.conf contents
# This script restores the config from canonical copy if tampered
@ -386,7 +386,7 @@ EOF
echo "✓ Created enforcement script: $enforce_script"
# Create unlock script with psychological delay
cat >"$unlock_script" <<'EOF'
cat > "$unlock_script" << 'EOF'
#!/bin/bash
# Unlock shutdown schedule config for editing with smart friction
# This script:
@ -602,7 +602,7 @@ EOF
echo "✓ Created unlock script: $unlock_script"
# Create path watcher unit
cat >"$guard_path" <<'EOF'
cat > "$guard_path" << 'EOF'
[Unit]
Description=Watch /etc/shutdown-schedule.conf and trigger enforcement
@ -617,7 +617,7 @@ EOF
echo "✓ Created path watcher: $guard_path"
# Create enforcement service
cat >"$guard_service" <<'EOF'
cat > "$guard_service" << 'EOF'
[Unit]
Description=Enforce canonical /etc/shutdown-schedule.conf contents
After=local-fs.target
@ -652,7 +652,7 @@ create_shutdown_service() {
local service_file="/etc/systemd/system/day-specific-shutdown.service"
cat >"$service_file" <<'EOF'
cat > "$service_file" << 'EOF'
[Unit]
Description=Automatic PC shutdown with day-specific time windows
DefaultDependencies=false
@ -686,7 +686,7 @@ create_shutdown_timer() {
# Generate timer entries dynamically from earliest_hour to MORNING_END_HOUR
# This ensures timer fires at all possible shutdown times
{
cat <<EOF
cat << EOF
[Unit]
Description=Timer for automatic PC shutdown with day-specific windows
Requires=day-specific-shutdown.service
@ -707,7 +707,7 @@ EOF
fi
done
cat <<EOF
cat << EOF
Persistent=false
AccuracySec=1s
WakeSystem=false
@ -716,7 +716,7 @@ RandomizedDelaySec=0
[Install]
WantedBy=timers.target
EOF
} >"$timer_file"
} > "$timer_file"
echo "✓ Created systemd timer: $timer_file"
echo " Timer covers: ${earliest_hour}:00 to 0${SCHEDULE_MORNING_END_HOUR}:00"
@ -730,7 +730,7 @@ create_management_script() {
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
cat >"$script_file" <<'EOF'
cat > "$script_file" << 'EOF'
#!/bin/bash
# Day-Specific Auto-Shutdown Manager
# Provides easy management of the day-specific shutdown feature
@ -822,7 +822,7 @@ create_shutdown_check_script() {
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
cat >"$check_script" <<'EOF'
cat > "$check_script" << 'EOF'
#!/bin/bash
# Smart day-specific shutdown check script
# Reads shutdown windows from /etc/shutdown-schedule.conf
@ -937,7 +937,7 @@ install_monitor_service() {
local monitor_watchdog_service="/etc/systemd/system/shutdown-timer-monitor-watchdog.service"
# Create the monitor script
cat >"$monitor_script" <<'EOF'
cat > "$monitor_script" << 'EOF'
#!/bin/bash
# Shutdown timer monitor script
# Watches the day-specific-shutdown timer and re-enables it if disabled
@ -1019,7 +1019,7 @@ EOF
echo "✓ Created monitor script: $monitor_script"
# Create the monitor service with RefuseManualStop to prevent manual stopping
cat >"$monitor_service" <<'EOF'
cat > "$monitor_service" << 'EOF'
[Unit]
Description=Shutdown Timer Monitor and Auto-Restore Service
After=network-online.target day-specific-shutdown.timer
@ -1051,7 +1051,7 @@ EOF
echo "✓ Created monitor service: $monitor_service"
# Create a watchdog timer that ensures the monitor stays running
cat >"$monitor_watchdog_service" <<'EOF'
cat > "$monitor_watchdog_service" << 'EOF'
[Unit]
Description=Watchdog for Shutdown Timer Monitor
After=multi-user.target
@ -1064,7 +1064,7 @@ EOF
echo "✓ Created watchdog service: $monitor_watchdog_service"
cat >"$monitor_timer" <<'EOF'
cat > "$monitor_timer" << 'EOF'
[Unit]
Description=Watchdog Timer for Shutdown Timer Monitor
After=multi-user.target
@ -1117,13 +1117,13 @@ test_setup() {
echo ""
echo "Timer status:"
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is enabled"
else
echo "✗ Timer is not enabled"
fi
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"
@ -1131,13 +1131,13 @@ test_setup() {
echo ""
echo "Monitor status:"
if systemctl is-enabled shutdown-timer-monitor.service &>/dev/null; then
if systemctl is-enabled shutdown-timer-monitor.service &> /dev/null; then
echo "✓ Monitor is enabled"
else
echo "✗ Monitor is not enabled"
fi
if systemctl is-active shutdown-timer-monitor.service &>/dev/null; then
if systemctl is-active shutdown-timer-monitor.service &> /dev/null; then
echo "✓ Monitor is active"
else
echo "✗ Monitor is not active"
@ -1145,13 +1145,13 @@ test_setup() {
echo ""
echo "Watchdog timer status:"
if systemctl is-enabled shutdown-timer-monitor-watchdog.timer &>/dev/null; then
if systemctl is-enabled shutdown-timer-monitor-watchdog.timer &> /dev/null; then
echo "✓ Watchdog timer is enabled"
else
echo "✗ Watchdog timer is not enabled"
fi
if systemctl is-active shutdown-timer-monitor-watchdog.timer &>/dev/null; then
if systemctl is-active shutdown-timer-monitor-watchdog.timer &> /dev/null; then
echo "✓ Watchdog timer is active"
else
echo "✗ Watchdog timer is not active"
@ -1162,9 +1162,9 @@ test_setup() {
local config_file="/etc/shutdown-schedule.conf"
local canonical_file="/usr/local/share/locked-shutdown-schedule.conf"
if [[ -f "$config_file" ]]; then
if [[ -f $config_file ]]; then
echo "✓ Config file exists"
if lsattr "$config_file" 2>/dev/null | grep -q '^....i'; then
if lsattr "$config_file" 2> /dev/null | grep -q '^....i'; then
echo "✓ Config file is immutable"
else
echo "✗ Config file is NOT immutable"
@ -1173,19 +1173,19 @@ test_setup() {
echo "✗ Config file missing"
fi
if [[ -f "$canonical_file" ]]; then
if [[ -f $canonical_file ]]; then
echo "✓ Canonical copy exists"
else
echo "✗ Canonical copy missing"
fi
if systemctl is-enabled shutdown-schedule-guard.path &>/dev/null; then
if systemctl is-enabled shutdown-schedule-guard.path &> /dev/null; then
echo "✓ Config guard path watcher is enabled"
else
echo "✗ Config guard path watcher is not enabled"
fi
if systemctl is-active shutdown-schedule-guard.path &>/dev/null; then
if systemctl is-active shutdown-schedule-guard.path &> /dev/null; then
echo "✓ Config guard path watcher is active"
else
echo "✗ Config guard path watcher is not active"
@ -1199,7 +1199,7 @@ test_setup() {
echo ""
echo "Next scheduled checks:"
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available"
systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available"
}
# Display the shutdown schedule (used in multiple places)
@ -1338,18 +1338,18 @@ enable_midnight_shutdown() {
# Parse command line arguments
case "${1:-enable}" in
"enable")
"enable")
check_sudo "$@"
enable_midnight_shutdown
;;
"status")
"status")
check_sudo "$@"
show_current_status
;;
"help" | "-h" | "--help")
"help" | "-h" | "--help")
show_usage
;;
*)
*)
echo "Error: Unknown command '$1'"
echo ""
show_usage

View File

@ -64,30 +64,30 @@ was_booted_in_window_today() {
boot_time=""
# Get the last boot time using multiple methods for reliability
if command -v uptime &>/dev/null; then
if command -v uptime &> /dev/null; then
# Method 1: Calculate boot time from uptime
local uptime_seconds
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
if [[ $uptime_seconds -gt 0 ]]; then
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi
fi
# Method 2: Use systemd if available (fallback)
if [[ -z $boot_time ]] && command -v systemctl &>/dev/null; then
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2>/dev/null || echo "")
if [[ -z $boot_time ]] && command -v systemctl &> /dev/null; then
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2> /dev/null || echo "")
if [[ -n $boot_time ]]; then
# This gives us relative time, need to calculate absolute time
local current_time uptime_sec
current_time=$(date +%s)
uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
uptime_sec=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S")
fi
fi
# Method 3: Use who -b (fallback)
if [[ -z $boot_time ]] && command -v who &>/dev/null; then
boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "")
if [[ -z $boot_time ]] && command -v who &> /dev/null; then
boot_time=$(who -b | awk '{print $3, $4}' 2> /dev/null || echo "")
if [[ -n $boot_time ]]; then
boot_time="$today $boot_time"
fi
@ -96,7 +96,7 @@ was_booted_in_window_today() {
# Method 4: Use /proc/uptime as final fallback
if [[ -z $boot_time ]]; then
local uptime_seconds
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi
@ -151,12 +151,12 @@ show_startup_warning() {
logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today"
# Try to show desktop notification if possible
if command -v notify-send &>/dev/null && [[ -n $DISPLAY ]]; then
if command -v notify-send &> /dev/null && [[ -n $DISPLAY ]]; then
if [[ $EUID -eq 0 ]]; then
# Running as root, send notification as user
sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true
sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true
else
notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true
notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true
fi
fi
@ -173,7 +173,7 @@ create_monitoring_service() {
local service_file="/etc/systemd/system/pc-startup-monitor.service"
cat >"$service_file" <<'EOF'
cat > "$service_file" << 'EOF'
[Unit]
Description=PC Startup Time Monitor
After=multi-user.target
@ -201,7 +201,7 @@ create_monitoring_timer() {
local timer_file="/etc/systemd/system/pc-startup-monitor.timer"
cat >"$timer_file" <<'EOF'
cat > "$timer_file" << 'EOF'
[Unit]
Description=Timer for PC startup monitoring
Requires=pc-startup-monitor.service
@ -226,7 +226,7 @@ create_monitoring_script() {
local script_file="/usr/local/bin/pc-startup-check.sh"
cat >"$script_file" <<'EOF'
cat > "$script_file" << 'EOF'
#!/bin/bash
# PC Startup Time Monitor Check Script
# Monitors if PC was turned on during expected hours on specific days
@ -344,7 +344,7 @@ create_management_script() {
local script_file="/usr/local/bin/pc-startup-monitor-manager.sh"
cat >"$script_file" <<'EOF'
cat > "$script_file" << 'EOF'
#!/bin/bash
# PC Startup Monitor Manager
# Provides easy management of the PC startup monitoring feature
@ -450,13 +450,13 @@ test_setup() {
echo ""
echo "Timer status:"
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then
echo "✓ Timer is enabled"
else
echo "✗ Timer is not enabled"
fi
if systemctl is-active pc-startup-monitor.timer &>/dev/null; then
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"

View File

@ -36,7 +36,7 @@ fail() {
}
usage() {
cat <<EOF
cat << EOF
Usage: $SCRIPT_NAME [options]
Options:
@ -89,14 +89,14 @@ done
REPO_DIR="$INSTALL_ROOT/unreal-mcp"
# ---------- Dependencies ----------
require_cmd() { command -v "$1" >/dev/null 2>&1; }
require_cmd() { command -v "$1" > /dev/null 2>&1; }
ensure_packages_arch() {
# Install with pacman using sudo when needed; keep idempotent with --needed
local pkgs=(git jq uv python rsync)
local to_install=()
for p in "${pkgs[@]}"; do
if ! pacman -Qi "$p" >/dev/null 2>&1; then
if ! pacman -Qi "$p" > /dev/null 2>&1; then
to_install+=("$p")
fi
done
@ -172,7 +172,7 @@ install_launcher() {
local python_dir="$REPO_DIR/Python"
local launcher="$bin_dir/unreal-mcp-server"
mkdir -p "$bin_dir"
cat >"$launcher" <<EOF
cat > "$launcher" << EOF
#!/bin/bash
set -euo pipefail
exec uv --directory "$python_dir" run unreal_mcp_server.py "\${1:-}" < /dev/null
@ -198,7 +198,7 @@ configure_continue() {
local tmp_file
tmp_file="$(mktemp)"
if [[ ! -f $cont_cfg ]]; then
cat >"$tmp_file" <<JSON
cat > "$tmp_file" << JSON
{
"mcpServers": {
"unrealMCP": {
@ -220,7 +220,7 @@ JSON
command: "uv",
args: ["--directory", $dir, "run", "unreal_mcp_server.py"]
}
' "$cont_cfg" >"$tmp_file" && mv "$tmp_file" "$cont_cfg"
' "$cont_cfg" > "$tmp_file" && mv "$tmp_file" "$cont_cfg"
fi
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$cont_cfg"; fi
@ -246,12 +246,12 @@ configure_vscode_user_mcp() {
local candidates=(code code-insiders codium)
local found_any=false
for cli in "${candidates[@]}"; do
if ! command -v "$cli" >/dev/null 2>&1; then
if ! command -v "$cli" > /dev/null 2>&1; then
continue
fi
found_any=true
log "Registering MCP server in VS Code user profile via: $cli --add-mcp"
if "$cli" --add-mcp "$json" >"/tmp/${cli}-add-mcp.log" 2>&1; then
if "$cli" --add-mcp "$json" > "/tmp/${cli}-add-mcp.log" 2>&1; then
log "[$cli] user profile: unrealMCP added/updated"
else
sed -n '1,200p' "/tmp/${cli}-add-mcp.log" || true
@ -280,7 +280,7 @@ configure_vscode_user_mcp() {
local name
for name in "${unreal_profiles[@]}"; do
log "[$cli] Adding unrealMCP to profile: $name"
if "$cli" --profile "$name" --add-mcp "$json" >"/tmp/${cli}-add-mcp-${name// /_}.log" 2>&1; then
if "$cli" --profile "$name" --add-mcp "$json" > "/tmp/${cli}-add-mcp-${name// /_}.log" 2>&1; then
log "[$cli] profile '$name': unrealMCP added/updated"
else
sed -n '1,200p' "/tmp/${cli}-add-mcp-${name// /_}.log" || true
@ -306,7 +306,7 @@ install_plugin_into_project() {
local upath="$PROJECT_UPROJECT"
if [[ -d $upath ]]; then
# Resolve .uproject in the provided directory
mapfile -t _uprojects < <(find "$upath" -maxdepth 1 -type f -name "*.uproject" 2>/dev/null || true)
mapfile -t _uprojects < <(find "$upath" -maxdepth 1 -type f -name "*.uproject" 2> /dev/null || true)
if [[ ${#_uprojects[@]} -eq 0 ]]; then
fail "--project directory '$upath' contains no .uproject files"
elif [[ ${#_uprojects[@]} -gt 1 ]]; then
@ -348,7 +348,7 @@ print_summary() {
if [[ -n $RESOLVED_PROJECT_DIR ]]; then
plugin_dest="$RESOLVED_PROJECT_DIR/Plugins/UnrealMCP"
fi
cat <<EOF
cat << EOF
============================================
Unreal MCP setup complete
============================================

View File

@ -13,7 +13,7 @@ echo -e "${BLUE}=== Unreal MCP Installer for Arch Linux ===${NC}"
# Check dependencies
echo -e "${BLUE}Checking dependencies...${NC}"
for cmd in git python pip; do
if ! command -v $cmd &>/dev/null; then
if ! command -v $cmd &> /dev/null; then
echo -e "${RED}Error: $cmd is not installed. Please install it (e.g., sudo pacman -S $cmd)${NC}"
exit 1
fi
@ -29,7 +29,7 @@ fi
# Validate path
# Expand tilde if present
PROJECT_PATH="${PROJECT_PATH/#\~/$HOME}"
PROJECT_PATH=$(realpath "$PROJECT_PATH" 2>/dev/null || echo "")
PROJECT_PATH=$(realpath "$PROJECT_PATH" 2> /dev/null || echo "")
if [ -z "$PROJECT_PATH" ] || [ ! -d "$PROJECT_PATH" ]; then
echo -e "${RED}Error: Invalid directory: $PROJECT_PATH${NC}"
@ -79,26 +79,26 @@ fi
echo "Installing dependencies in virtual environment..."
# shellcheck source=/dev/null
source "$VENV_DIR/bin/activate"
pip install --upgrade pip >/dev/null
pip install "mcp>=0.1.0" >/dev/null
pip install --upgrade pip > /dev/null
pip install "mcp>=0.1.0" > /dev/null
# Patch unreal_mcp_bridge.py for newer mcp package compatibility
# The newer mcp package (1.x) renamed 'description' parameter to 'instructions'
BRIDGE_SCRIPT="$MCP_DIR/unreal_mcp_bridge.py"
if grep -q 'description="Unreal Engine integration' "$BRIDGE_SCRIPT" 2>/dev/null; then
if grep -q 'description="Unreal Engine integration' "$BRIDGE_SCRIPT" 2> /dev/null; then
echo "Patching unreal_mcp_bridge.py for mcp package compatibility..."
sed -i 's/description="Unreal Engine integration through the Model Context Protocol"/instructions="Unreal Engine integration through the Model Context Protocol"/' "$BRIDGE_SCRIPT"
fi
# Fix case-sensitive includes for Linux (Windows is case-insensitive, Linux is not)
echo "Fixing case-sensitive includes for Linux..."
find "$MCP_PLUGIN_DIR/Source/" \( -name "*.cpp" -o -name "*.h" \) -exec sed -i 's/HAL\/PlatformFilemanager\.h/HAL\/PlatformFileManager.h/g' {} + 2>/dev/null || true
find "$MCP_PLUGIN_DIR/Source/" \( -name "*.cpp" -o -name "*.h" \) -exec sed -i 's/HAL\/PlatformFilemanager\.h/HAL\/PlatformFileManager.h/g' {} + 2> /dev/null || true
# Create Linux Run Script
RUN_SCRIPT="$MCP_DIR/run_unreal_mcp.sh"
echo -e "${BLUE}Creating run script at $RUN_SCRIPT...${NC}"
cat <<EOF >"$RUN_SCRIPT"
cat << EOF > "$RUN_SCRIPT"
#!/bin/bash
set -e
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
@ -115,7 +115,7 @@ echo -e "${BLUE}=== Configuration Setup ===${NC}"
# Python script to update JSON configs
CONFIG_UPDATER_SCRIPT=$(mktemp)
cat <<EOF >"$CONFIG_UPDATER_SCRIPT"
cat << EOF > "$CONFIG_UPDATER_SCRIPT"
import json
import os
import sys
@ -190,7 +190,7 @@ MCP_JSON="$VSCODE_DIR/mcp.json"
if [ ! -f "$MCP_JSON" ]; then
echo -e "${BLUE}Creating workspace MCP config at $MCP_JSON...${NC}"
cat <<EOF >"$MCP_JSON"
cat << EOF > "$MCP_JSON"
{
"mcpServers": {
"unreal": {
@ -232,7 +232,7 @@ echo -e "${YELLOW}$RUN_SCRIPT${NC}"
echo
echo "For VS Code (User Settings), add this to your settings.json:"
echo -e "${GREEN}"
cat <<EOF
cat << EOF
"mcpServers": {
"unreal": {
"command": "$RUN_SCRIPT",

View File

@ -65,7 +65,7 @@ check_root() {
}
save_config() {
cat >"$CONFIG_FILE" <<EOF
cat > "$CONFIG_FILE" << EOF
# Raspberry Pi Setup - Auto-generated config
# This file is gitignored and stores discovered settings
@ -88,7 +88,7 @@ EOF
generate_password() {
local length="${1:-16}"
local chars
chars=$(dd if=/dev/urandom bs=256 count=1 2>/dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
chars=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
echo "$chars"
}
@ -106,22 +106,22 @@ auto_generate_pi_password() {
ensure_dependencies() {
local missing_packages=()
if ! command -v nmap &>/dev/null; then
if ! command -v nmap &> /dev/null; then
missing_packages+=("nmap")
fi
if ! command -v sshpass &>/dev/null; then
if ! command -v sshpass &> /dev/null; then
missing_packages+=("sshpass")
fi
if [[ ${#missing_packages[@]} -gt 0 ]]; then
log_info "Installing missing packages: ${missing_packages[*]}"
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm "${missing_packages[@]}"
elif command -v apt-get &>/dev/null; then
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
elif command -v dnf &>/dev/null; then
elif command -v dnf &> /dev/null; then
sudo dnf install -y "${missing_packages[@]}"
else
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
@ -147,8 +147,8 @@ discover_remote_laptop() {
log_info "Scanning network for SSH-enabled devices (using nmap)..."
local ssh_hosts
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)
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
die "No SSH-enabled devices found on network"
@ -190,13 +190,13 @@ discover_remote_laptop() {
log_info "[$idx/$host_count] $ip - Trying SSH key access with common usernames..."
for try_user in "${users[@]}"; do
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2> /dev/null | grep -q "ok"; then
log_success "[$idx/$host_count] $ip - SSH key access confirmed with user '$try_user'!"
found_user="$try_user"
log_info "[$idx/$host_count] $ip - Checking for SD card..."
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)
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
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
@ -241,7 +241,7 @@ setup_ssh_key_to_remote() {
local remote_host="$1"
local remote_user="$2"
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2>/dev/null; then
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2> /dev/null; then
log_success "SSH key authentication to ${remote_user}@${remote_host} already configured"
return 0
fi
@ -255,11 +255,11 @@ setup_ssh_key_to_remote() {
log_info "Copying SSH key to remote host (you may be prompted for password)..."
if command -v ssh-copy-id &>/dev/null; then
if command -v ssh-copy-id &> /dev/null; then
ssh-copy-id -o StrictHostKeyChecking=no "${remote_user}@${remote_host}"
else
local pub_key
pub_key=$(cat ~/.ssh/id_ed25519.pub 2>/dev/null || cat ~/.ssh/id_rsa.pub)
pub_key=$(cat ~/.ssh/id_ed25519.pub 2> /dev/null || cat ~/.ssh/id_rsa.pub)
ssh -o StrictHostKeyChecking=no "${remote_user}@${remote_host}" "mkdir -p ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
fi
@ -287,7 +287,7 @@ download_raspberry_pi_os() {
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)
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
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
rm -f "$image_file"
@ -300,18 +300,18 @@ download_raspberry_pi_os() {
log_info "Downloading Raspberry Pi OS Lite (64-bit)..."
log_info "This may take a while depending on your internet connection..."
if command -v aria2c &>/dev/null; then
if command -v aria2c &> /dev/null; then
aria2c -x 4 -c -d "$download_dir" --out="raspios.img.xz" "$image_url" >&2
elif command -v wget &>/dev/null; then
elif command -v wget &> /dev/null; then
wget --continue --show-progress -O "$image_file" "$image_url" >&2
elif command -v curl &>/dev/null; then
elif command -v curl &> /dev/null; then
curl -L -C - -o "$image_file" "$image_url" --progress-bar >&2
else
die "No download tool available. Install wget, curl, or aria2c"
fi
local actual_size
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
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
die "Download incomplete: got $actual_size bytes, expected $expected_size"
fi
@ -381,8 +381,8 @@ phase_flash_local() {
log_info "Unmounting partitions on $SD_CARD_DEVICE..."
for partition in "${SD_CARD_DEVICE}"*; do
if mountpoint -q "$partition" 2>/dev/null || mount | grep -q "$partition"; then
umount "$partition" 2>/dev/null || true
if mountpoint -q "$partition" 2> /dev/null || mount | grep -q "$partition"; then
umount "$partition" 2> /dev/null || true
fi
done
@ -394,7 +394,7 @@ phase_flash_local() {
# Configure headless boot
log_info "Configuring headless boot..."
sleep 2
partprobe "$SD_CARD_DEVICE" 2>/dev/null || true
partprobe "$SD_CARD_DEVICE" 2> /dev/null || true
sleep 2
local boot_partition
@ -413,7 +413,7 @@ phase_flash_local() {
touch "$boot_mount/ssh"
log_success "SSH enabled"
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
echo "${PI_USER}:${encrypted_password}" > "$boot_mount/userconf.txt"
log_success "User '$PI_USER' configured"
local root_partition
@ -428,7 +428,7 @@ phase_flash_local() {
mkdir -p "$root_mount"
mount "$root_partition" "$root_mount"
echo "$PI_HOSTNAME" >"$root_mount/etc/hostname"
echo "$PI_HOSTNAME" > "$root_mount/etc/hostname"
sed -i "s/raspberrypi/$PI_HOSTNAME/g" "$root_mount/etc/hosts"
log_success "Hostname set to '$PI_HOSTNAME'"
@ -473,7 +473,7 @@ phase_flash_remote() {
log_info "Auto-detecting SD card on remote laptop..."
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)
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
die "No SD card detected on remote laptop. Please insert an SD card and try again."
@ -487,7 +487,7 @@ phase_flash_remote() {
SD_CARD_DEVICE="$sd_device"
# shellcheck disable=SC2029 # Intentional client-side expansion
if ! ssh "$remote" "[[ -b '$SD_CARD_DEVICE' ]]" 2>/dev/null; then
if ! ssh "$remote" "[[ -b '$SD_CARD_DEVICE' ]]" 2> /dev/null; then
die "Device $SD_CARD_DEVICE does not exist on remote laptop"
fi
@ -539,8 +539,8 @@ phase_execute_remote() {
log_info "Unmounting partitions on $SD_CARD_DEVICE..."
for partition in "${SD_CARD_DEVICE}"*; do
if mountpoint -q "$partition" 2>/dev/null || mount | grep -q "$partition"; then
umount "$partition" 2>/dev/null || true
if mountpoint -q "$partition" 2> /dev/null || mount | grep -q "$partition"; then
umount "$partition" 2> /dev/null || true
fi
done
@ -551,7 +551,7 @@ phase_execute_remote() {
log_info "Configuring headless boot..."
sleep 2
partprobe "$SD_CARD_DEVICE" 2>/dev/null || true
partprobe "$SD_CARD_DEVICE" 2> /dev/null || true
sleep 2
local boot_partition
@ -571,7 +571,7 @@ phase_execute_remote() {
log_success "SSH enabled"
if [[ -n $encrypted_password ]]; then
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
echo "${PI_USER}:${encrypted_password}" > "$boot_mount/userconf.txt"
log_success "User '$PI_USER' configured"
fi
@ -587,7 +587,7 @@ phase_execute_remote() {
mkdir -p "$root_mount"
mount "$root_partition" "$root_mount"
echo "$PI_HOSTNAME" >"$root_mount/etc/hostname"
echo "$PI_HOSTNAME" > "$root_mount/etc/hostname"
sed -i "s/raspberrypi/$PI_HOSTNAME/g" "$root_mount/etc/hosts"
log_success "Hostname set to '$PI_HOSTNAME'"
@ -606,7 +606,7 @@ phase_execute_remote() {
# =============================================================================
show_help() {
cat <<'EOF'
cat << 'EOF'
Raspberry Pi SD Card Flash Script
Usage: ./raspberry_pi_flash_sd.sh <command>

View File

@ -71,7 +71,7 @@ check_root() {
}
save_config() {
cat >"$CONFIG_FILE" <<EOF
cat > "$CONFIG_FILE" << EOF
# Raspberry Pi Nextcloud Setup - Auto-generated config
# This file is gitignored and stores discovered settings
@ -97,7 +97,7 @@ EOF
generate_password() {
local length="${1:-16}"
local chars
chars=$(dd if=/dev/urandom bs=256 count=1 2>/dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
chars=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
echo "$chars"
}
@ -112,7 +112,7 @@ wait_for_apt_lock() {
local max_wait=600
local waited=0
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock > /dev/null 2>&1; do
if [[ $waited -eq 0 ]]; then
log_info "Waiting for other apt/dpkg processes to finish..."
pgrep -a 'apt|dpkg' | head -5 >&2 || true
@ -139,22 +139,22 @@ wait_for_apt_lock() {
ensure_dependencies() {
local missing_packages=()
if ! command -v nmap &>/dev/null; then
if ! command -v nmap &> /dev/null; then
missing_packages+=("nmap")
fi
if ! command -v sshpass &>/dev/null; then
if ! command -v sshpass &> /dev/null; then
missing_packages+=("sshpass")
fi
if [[ ${#missing_packages[@]} -gt 0 ]]; then
log_info "Installing missing packages: ${missing_packages[*]}"
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm "${missing_packages[@]}"
elif command -v apt-get &>/dev/null; then
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
elif command -v dnf &>/dev/null; then
elif command -v dnf &> /dev/null; then
sudo dnf install -y "${missing_packages[@]}"
else
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
@ -179,9 +179,9 @@ discover_raspberry_pi() {
local pi_ip=""
# Try resolving hostname directly
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
pi_ip=$(getent hosts "$PI_HOSTNAME" 2> /dev/null | awk '{print $1}' | head -1) || true
if [[ -z $pi_ip ]]; then
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2> /dev/null | awk '{print $1}' | head -1) || true
fi
if [[ -n $pi_ip ]]; then
@ -191,10 +191,10 @@ discover_raspberry_pi() {
fi
log_info "Hostname resolution failed, scanning network..."
nmap -sn -T4 "$network" &>/dev/null || true
nmap -sn -T4 "$network" &> /dev/null || true
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
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
die "No SSH-enabled devices found. Is the Pi connected and booted?"
@ -205,13 +205,13 @@ discover_raspberry_pi() {
for ip in $ssh_hosts; do
log_info "Trying $ip with user '$PI_USER'..."
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "hostname" 2>/dev/null | grep -qi "$PI_HOSTNAME"; then
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "hostname" 2> /dev/null | grep -qi "$PI_HOSTNAME"; then
log_success "Found Raspberry Pi at $ip"
echo "$ip"
return
fi
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "echo ok" 2> /dev/null | grep -q "ok"; then
log_success "Found device responding to Pi credentials at $ip"
echo "$ip"
return
@ -250,7 +250,7 @@ phase_configure_system() {
log_info "Hardening SSH configuration..."
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
cat >>/etc/ssh/sshd_config.d/hardening.conf <<'EOF'
cat >> /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Security hardening
PermitRootLogin no
PasswordAuthentication yes
@ -283,7 +283,7 @@ EOF
ufw --force enable
log_info "Configuring fail2ban..."
cat >/etc/fail2ban/jail.local <<'EOF'
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
@ -301,7 +301,7 @@ EOF
systemctl restart fail2ban
log_info "Enabling automatic security updates..."
cat >/etc/apt/apt.conf.d/50unattended-upgrades <<'EOF'
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Raspbian,codename=${distro_codename},label=Raspbian";
@ -310,7 +310,7 @@ Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
EOF
cat >/etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
cat > /etc/apt/apt.conf.d/20auto-upgrades << 'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
@ -365,14 +365,14 @@ phase_install_nextcloud() {
local db_password
db_password=$(generate_password 32)
mysql -u root <<EOF
mysql -u root << EOF
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER IF NOT EXISTS 'nextcloud'@'localhost' IDENTIFIED BY '${db_password}';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
EOF
echo "$db_password" >/root/.nextcloud_db_password
echo "$db_password" > /root/.nextcloud_db_password
chmod 600 /root/.nextcloud_db_password
log_success "MariaDB configured"
@ -393,7 +393,7 @@ EOF
# Configure Apache
log_info "Configuring Apache..."
cat >/etc/apache2/sites-available/nextcloud.conf <<'EOF'
cat > /etc/apache2/sites-available/nextcloud.conf << 'EOF'
<VirtualHost *:80>
ServerAdmin admin@localhost
DocumentRoot /var/www/nextcloud
@ -442,7 +442,7 @@ EOF
sed -i 's/;date.timezone =.*/date.timezone = Europe\/Warsaw/' "$php_ini"
if ! grep -q "opcache.interned_strings_buffer" "$php_ini"; then
cat >>"$php_ini" <<'EOF'
cat >> "$php_ini" << 'EOF'
; Nextcloud optimizations
opcache.enable=1
@ -514,7 +514,7 @@ EOF
# Add cron job
(
crontab -u www-data -l 2>/dev/null || true
crontab -u www-data -l 2> /dev/null || true
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php"
) | sort -u | crontab -u www-data -
@ -560,7 +560,7 @@ phase_fix_issues() {
# Ensure cron job exists and is correct
(
crontab -u www-data -l 2>/dev/null | grep -v "cron.php"
crontab -u www-data -l 2> /dev/null | grep -v "cron.php"
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php"
) | crontab -u www-data -
@ -613,7 +613,7 @@ phase_fix_issues() {
# Create extension file for SAN (Subject Alternative Names)
# This allows the certificate to be valid for hostname, IP, and .local
cat >"$ssl_dir/server.ext" <<EXTEOF
cat > "$ssl_dir/server.ext" << EXTEOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
@ -650,7 +650,7 @@ EXTEOF
log_info "CA certificate available at: https://$PI_HOSTNAME/ca/nextcloud-ca.crt"
# Create HTTPS Apache config
cat >/etc/apache2/sites-available/nextcloud-ssl.conf <<EOF
cat > /etc/apache2/sites-available/nextcloud-ssl.conf << EOF
<VirtualHost *:443>
ServerAdmin admin@localhost
DocumentRoot /var/www/nextcloud
@ -837,14 +837,14 @@ phase_setup_ssl() {
# Set up automatic DuckDNS updates (cron) - auto-detect public IP
log_info "Setting up automatic DuckDNS IP updates..."
mkdir -p /opt/duckdns
cat >/opt/duckdns/duck.sh <<DUCKEOF
cat > /opt/duckdns/duck.sh << DUCKEOF
#!/bin/bash
echo url="https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=" | curl -k -o /opt/duckdns/duck.log -K -
DUCKEOF
chmod 700 /opt/duckdns/duck.sh
# Add cron job for DuckDNS update every 5 minutes
(crontab -l 2>/dev/null || true) | grep -v "duckdns" | {
(crontab -l 2> /dev/null || true) | grep -v "duckdns" | {
cat
echo "*/5 * * * * /opt/duckdns/duck.sh >/dev/null 2>&1"
} | crontab -
@ -857,7 +857,7 @@ DUCKEOF
local attempts=0
while [[ $dns_ip != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do
sleep 5
dns_ip=$(dig +short "$full_domain" 2>/dev/null | tail -1) || true
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
@ -869,7 +869,7 @@ DUCKEOF
fi
# Install certbot if not present
if ! command -v certbot &>/dev/null; then
if ! command -v certbot &> /dev/null; then
log_info "Installing certbot..."
DEBIAN_FRONTEND=noninteractive apt-get install -y certbot python3-certbot-apache
fi
@ -878,7 +878,7 @@ DUCKEOF
log_info "Obtaining Let's Encrypt certificate..."
# First update Apache config with the new domain
cat >/etc/apache2/sites-available/nextcloud-ssl.conf <<EOF
cat > /etc/apache2/sites-available/nextcloud-ssl.conf << EOF
<VirtualHost *:443>
ServerAdmin ${LETSENCRYPT_EMAIL}
DocumentRoot /var/www/nextcloud
@ -1026,7 +1026,7 @@ phase_install_remote() {
log_info "Using Raspberry Pi at: $pi_ip"
# Remove old host key if present
ssh-keygen -R "$pi_ip" 2>/dev/null || true
ssh-keygen -R "$pi_ip" 2> /dev/null || true
log_info "Copying script to Pi..."
sshpass -p "$PI_PASSWORD" scp -o StrictHostKeyChecking=no "$0" "${PI_USER}@${pi_ip}:/tmp/raspberry_pi_nextcloud.sh"
@ -1087,7 +1087,7 @@ phase_install_ca() {
# Use SSH with sudo to cat the file (since it's in a protected directory)
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
"${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
die "Failed to download CA certificate"
@ -1127,12 +1127,12 @@ phase_install_ca() {
log_info "Installing CA in browser certificate stores..."
# Chrome/Chromium (uses NSS)
if [[ -d ~/.pki/nssdb ]] || command -v certutil &>/dev/null; then
if [[ -d ~/.pki/nssdb ]] || command -v certutil &> /dev/null; then
mkdir -p ~/.pki/nssdb
if ! certutil -d sql:~/.pki/nssdb -L 2>/dev/null | grep -q "Nextcloud"; then
if ! certutil -d sql:~/.pki/nssdb -L 2> /dev/null | grep -q "Nextcloud"; then
# Initialize NSS db if needed
certutil -d sql:~/.pki/nssdb -N --empty-password 2>/dev/null || true
if certutil -d sql:~/.pki/nssdb -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null; then
certutil -d sql:~/.pki/nssdb -N --empty-password 2> /dev/null || true
if certutil -d sql:~/.pki/nssdb -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2> /dev/null; then
log_success "CA installed in Chrome/Chromium"
else
log_warning "Could not install in Chrome/Chromium NSS db"
@ -1147,8 +1147,8 @@ phase_install_ca() {
local installed=0
for profile_dir in ~/.mozilla/firefox/*.default* ~/.mozilla/firefox/*.esr*; do
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 &&
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
else
installed=1
@ -1163,9 +1163,9 @@ phase_install_ca() {
fi
# Add hostname to /etc/hosts if not present
if ! grep -q "$PI_HOSTNAME" /etc/hosts 2>/dev/null; then
if ! grep -q "$PI_HOSTNAME" /etc/hosts 2> /dev/null; then
log_info "Adding $PI_HOSTNAME to /etc/hosts..."
echo "$pi_ip $PI_HOSTNAME ${PI_HOSTNAME}.local" | sudo tee -a /etc/hosts >/dev/null
echo "$pi_ip $PI_HOSTNAME ${PI_HOSTNAME}.local" | sudo tee -a /etc/hosts > /dev/null
log_success "Added $PI_HOSTNAME to /etc/hosts"
else
log_info "$PI_HOSTNAME already in /etc/hosts"
@ -1173,7 +1173,7 @@ phase_install_ca() {
# Verify
log_info "Verifying HTTPS connection..."
if curl -s --max-time 5 "https://$PI_HOSTNAME/status.php" 2>/dev/null | grep -q "installed"; then
if curl -s --max-time 5 "https://$PI_HOSTNAME/status.php" 2> /dev/null | grep -q "installed"; then
log_success "HTTPS connection verified - no certificate warnings!"
else
log_warning "Could not verify HTTPS - you may need to restart your browser"
@ -1196,7 +1196,7 @@ phase_install_ca() {
# =============================================================================
show_help() {
cat <<'EOF'
cat << 'EOF'
Nextcloud Installation Script for Raspberry Pi
Usage: ./raspberry_pi_nextcloud.sh <command>

View File

@ -36,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
@ -76,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
@ -108,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..."
@ -131,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
@ -155,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"
@ -179,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)"
@ -204,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
@ -243,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"
@ -272,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
@ -350,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"

View File

@ -76,7 +76,7 @@ check_root() {
save_config() {
# Save discovered/used configuration to gitignored config file
cat >"$CONFIG_FILE" <<EOF
cat > "$CONFIG_FILE" << EOF
# Nextcloud Raspberry Pi Setup - Auto-generated config
# This file is gitignored and stores discovered settings
@ -103,7 +103,7 @@ generate_password() {
# Use /dev/urandom for randomness, base64 encode, take first N chars
# Using dd to avoid SIGPIPE with pipefail
local chars
chars=$(dd if=/dev/urandom bs=256 count=1 2>/dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
chars=$(dd if=/dev/urandom bs=256 count=1 2> /dev/null | tr -dc 'A-Za-z0-9!@#$%&*' | cut -c1-"$length")
echo "$chars"
}
@ -201,7 +201,7 @@ download_raspberry_pi_os() {
# Check if download exists and is complete
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)
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
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
rm -f "$image_file"
@ -216,11 +216,11 @@ download_raspberry_pi_os() {
# Try to use aria2c for faster download, fall back to wget/curl
# Redirect all output to stderr so it doesn't interfere with function return value
if command -v aria2c &>/dev/null; then
if command -v aria2c &> /dev/null; then
aria2c -x 4 -c -d "$download_dir" --out="raspios.img.xz" "$image_url" >&2
elif command -v wget &>/dev/null; then
elif command -v wget &> /dev/null; then
wget --continue --show-progress -O "$image_file" "$image_url" >&2
elif command -v curl &>/dev/null; then
elif command -v curl &> /dev/null; then
curl -L -C - -o "$image_file" "$image_url" --progress-bar >&2
else
die "No download tool available. Install wget, curl, or aria2c"
@ -228,7 +228,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)
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
die "Download incomplete: got $actual_size bytes, expected $expected_size"
fi
@ -258,8 +258,8 @@ flash_sd_card() {
# Unmount any mounted partitions
log_info "Unmounting partitions on $SD_CARD_DEVICE..."
for partition in "${SD_CARD_DEVICE}"*; do
if mountpoint -q "$partition" 2>/dev/null || mount | grep -q "$partition"; then
umount "$partition" 2>/dev/null || true
if mountpoint -q "$partition" 2> /dev/null || mount | grep -q "$partition"; then
umount "$partition" 2> /dev/null || true
fi
done
@ -277,7 +277,7 @@ configure_headless_boot() {
# Wait for partitions to be available
sleep 2
partprobe "$SD_CARD_DEVICE" 2>/dev/null || true
partprobe "$SD_CARD_DEVICE" 2> /dev/null || true
sleep 2
# Mount boot partition
@ -305,7 +305,7 @@ configure_headless_boot() {
read -r -s -p "WiFi Password: " wifi_password
echo
cat >"$boot_mount/wpa_supplicant.conf" <<EOF
cat > "$boot_mount/wpa_supplicant.conf" << EOF
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
@ -326,7 +326,7 @@ EOF
local encrypted_password
encrypted_password=$(echo "$PI_PASSWORD" | openssl passwd -6 -stdin)
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
echo "${PI_USER}:${encrypted_password}" > "$boot_mount/userconf.txt"
log_success "User '$PI_USER' configured"
# Set hostname
@ -342,7 +342,7 @@ EOF
mkdir -p "$root_mount"
mount "$root_partition" "$root_mount"
echo "$PI_HOSTNAME" >"$root_mount/etc/hostname"
echo "$PI_HOSTNAME" > "$root_mount/etc/hostname"
sed -i "s/raspberrypi/$PI_HOSTNAME/g" "$root_mount/etc/hosts"
log_success "Hostname set to '$PI_HOSTNAME'"
@ -387,7 +387,7 @@ setup_ssh_key_to_remote() {
local remote_user="$2"
# Check if we already have passwordless access
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2>/dev/null; then
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2> /dev/null; then
log_success "SSH key authentication to ${remote_user}@${remote_host} already configured"
return 0
fi
@ -406,7 +406,7 @@ setup_ssh_key_to_remote() {
ssh-copy-id -o StrictHostKeyChecking=accept-new "${remote_user}@${remote_host}"
# Verify it works
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2>/dev/null; then
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_host}" "echo 'SSH key works'" 2> /dev/null; then
log_success "SSH key authentication configured successfully"
return 0
else
@ -420,12 +420,12 @@ ensure_dependencies() {
local missing_packages=()
# Check for nmap (fast network scanning)
if ! command -v nmap &>/dev/null; then
if ! command -v nmap &> /dev/null; then
missing_packages+=("nmap")
fi
# Check for sshpass (for initial SSH key setup)
if ! command -v sshpass &>/dev/null; then
if ! command -v sshpass &> /dev/null; then
missing_packages+=("sshpass")
fi
@ -433,13 +433,13 @@ ensure_dependencies() {
log_info "Installing missing packages: ${missing_packages[*]}"
# Detect package manager and install
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm "${missing_packages[@]}"
elif command -v apt-get &>/dev/null; then
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y "${missing_packages[@]}"
elif command -v dnf &>/dev/null; then
elif command -v dnf &> /dev/null; then
sudo dnf install -y "${missing_packages[@]}"
elif command -v yum &>/dev/null; then
elif command -v yum &> /dev/null; then
sudo yum install -y "${missing_packages[@]}"
else
die "Could not detect package manager. Please install manually: ${missing_packages[*]}"
@ -470,9 +470,9 @@ discover_remote_laptop() {
log_info "Scanning network for SSH-enabled devices (using nmap)..."
local ssh_hosts
# First do a ping sweep to wake up hosts, then scan SSH port
nmap -sn -T4 "$network" &>/dev/null || true
nmap -sn -T4 "$network" &> /dev/null || true
# 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)
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
die "No SSH-enabled devices found on network"
@ -519,14 +519,14 @@ discover_remote_laptop() {
# Try each username
for try_user in "${users[@]}"; do
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
if ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=accept-new "${try_user}@${ip}" "echo ok" 2> /dev/null | grep -q "ok"; then
log_success "[$idx/$host_count] $ip - SSH key access confirmed with user '$try_user'!"
found_user="$try_user"
# Check if there's a removable device (SD card)
log_info "[$idx/$host_count] $ip - Checking for SD card..."
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)
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
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
@ -594,7 +594,7 @@ phase_flash_remote() {
# Auto-detect SD card on remote laptop
log_info "Auto-detecting SD card on remote laptop..."
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)
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
die "No SD card detected on remote laptop. Please insert an SD card and try again."
@ -610,7 +610,7 @@ phase_flash_remote() {
# Verify device exists on remote
# shellcheck disable=SC2029 # Intentional client-side expansion
if ! ssh "$remote" "[[ -b '$SD_CARD_DEVICE' ]]" 2>/dev/null; then
if ! ssh "$remote" "[[ -b '$SD_CARD_DEVICE' ]]" 2> /dev/null; then
die "Device $SD_CARD_DEVICE does not exist on remote laptop"
fi
@ -668,8 +668,8 @@ phase_flash_remote_execute() {
# Unmount any mounted partitions
log_info "Unmounting partitions on $SD_CARD_DEVICE..."
for partition in "${SD_CARD_DEVICE}"*; do
if mountpoint -q "$partition" 2>/dev/null || mount | grep -q "$partition"; then
umount "$partition" 2>/dev/null || true
if mountpoint -q "$partition" 2> /dev/null || mount | grep -q "$partition"; then
umount "$partition" 2> /dev/null || true
fi
done
@ -681,7 +681,7 @@ phase_flash_remote_execute() {
# Configure headless boot
log_info "Configuring headless boot..."
sleep 2
partprobe "$SD_CARD_DEVICE" 2>/dev/null || true
partprobe "$SD_CARD_DEVICE" 2> /dev/null || true
sleep 2
# Mount boot partition
@ -704,7 +704,7 @@ phase_flash_remote_execute() {
# Create userconf.txt for first user
if [[ -n $encrypted_password ]]; then
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
echo "${PI_USER}:${encrypted_password}" > "$boot_mount/userconf.txt"
log_success "User '$PI_USER' configured"
fi
@ -721,7 +721,7 @@ phase_flash_remote_execute() {
mkdir -p "$root_mount"
mount "$root_partition" "$root_mount"
echo "$PI_HOSTNAME" >"$root_mount/etc/hostname"
echo "$PI_HOSTNAME" > "$root_mount/etc/hostname"
sed -i "s/raspberrypi/$PI_HOSTNAME/g" "$root_mount/etc/hosts"
log_success "Hostname set to '$PI_HOSTNAME'"
@ -744,7 +744,7 @@ wait_for_apt_lock() {
local max_wait=600 # 10 minutes max
local waited=0
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do
while fuser /var/lib/dpkg/lock-frontend /var/lib/apt/lists/lock /var/cache/apt/archives/lock > /dev/null 2>&1; do
if [[ $waited -eq 0 ]]; then
log_info "Waiting for other apt/dpkg processes to finish..."
log_info "Current apt processes:"
@ -799,7 +799,7 @@ phase_configure() {
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Apply security settings
cat >>/etc/ssh/sshd_config.d/hardening.conf <<'EOF'
cat >> /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# Security hardening
PermitRootLogin no
PasswordAuthentication yes
@ -836,7 +836,7 @@ EOF
# Configure fail2ban
log_info "Configuring fail2ban..."
cat >/etc/fail2ban/jail.local <<'EOF'
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
@ -855,7 +855,7 @@ EOF
# Enable automatic security updates
log_info "Enabling automatic security updates..."
cat >/etc/apt/apt.conf.d/50unattended-upgrades <<'EOF'
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Raspbian,codename=${distro_codename},label=Raspbian";
@ -936,7 +936,7 @@ configure_mariadb() {
mysql -e "FLUSH PRIVILEGES;"
# Save password for later use
echo "$db_password" >/root/.nextcloud_db_password
echo "$db_password" > /root/.nextcloud_db_password
chmod 600 /root/.nextcloud_db_password
log_success "MariaDB configured"
@ -985,7 +985,7 @@ configure_apache() {
server_ip=$(hostname -I | awk '{print $1}')
# Create Apache virtual host
cat >/etc/apache2/sites-available/nextcloud.conf <<EOF
cat > /etc/apache2/sites-available/nextcloud.conf << EOF
<VirtualHost *:80>
ServerName $server_ip
DocumentRoot /var/www/nextcloud
@ -1035,7 +1035,7 @@ configure_php() {
sed -i 's/;date.timezone.*/date.timezone = Europe\/Warsaw/' "$php_ini"
# Configure OPcache
cat >>"$php_ini" <<'EOF'
cat >> "$php_ini" << 'EOF'
; Nextcloud OPcache settings
opcache.enable=1
@ -1047,7 +1047,7 @@ opcache.revalidate_freq=1
EOF
# Configure APCu
echo "apc.enable_cli=1" >>"/etc/php/${php_version}/mods-available/apcu.ini"
echo "apc.enable_cli=1" >> "/etc/php/${php_version}/mods-available/apcu.ini"
systemctl restart apache2
@ -1117,9 +1117,9 @@ setup_nextcloud_cron() {
log_info "Setting up Nextcloud background jobs..."
# Add cron job for background tasks
crontab -u www-data -l 2>/dev/null || echo "" | crontab -u www-data -
crontab -u www-data -l 2> /dev/null || echo "" | crontab -u www-data -
(
crontab -u www-data -l 2>/dev/null | grep -v 'nextcloud/cron.php'
crontab -u www-data -l 2> /dev/null | grep -v 'nextcloud/cron.php'
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php"
) | crontab -u www-data -
@ -1205,9 +1205,9 @@ discover_raspberry_pi() {
local pi_ip=""
# Try resolving hostname directly
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
pi_ip=$(getent hosts "$PI_HOSTNAME" 2> /dev/null | awk '{print $1}' | head -1) || true
if [[ -z $pi_ip ]]; then
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2> /dev/null | awk '{print $1}' | head -1) || true
fi
if [[ -n $pi_ip ]]; then
@ -1218,11 +1218,11 @@ discover_raspberry_pi() {
# Ping sweep to wake up hosts
log_info "Hostname resolution failed, scanning network..."
nmap -sn -T4 "$network" &>/dev/null || true
nmap -sn -T4 "$network" &> /dev/null || true
# Scan for SSH-enabled devices (excluding our IP and known laptop)
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
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
die "No new SSH-enabled devices found. Is the Pi connected and booted?"
@ -1235,14 +1235,14 @@ discover_raspberry_pi() {
log_info "Trying $ip with user '$PI_USER'..."
# Try with password
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "hostname" 2>/dev/null | grep -qi "$PI_HOSTNAME"; then
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "hostname" 2> /dev/null | grep -qi "$PI_HOSTNAME"; then
log_success "Found Raspberry Pi at $ip"
echo "$ip"
return
fi
# Even if hostname doesn't match, check if it's a fresh Pi responding to our credentials
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "echo ok" 2>/dev/null | grep -q "ok"; then
if sshpass -p "$PI_PASSWORD" ssh -o BatchMode=no -o ConnectTimeout=5 -o StrictHostKeyChecking=no "${PI_USER}@${ip}" "echo ok" 2> /dev/null | grep -q "ok"; then
log_success "Found device responding to Pi credentials at $ip"
echo "$ip"
return
@ -1306,7 +1306,7 @@ phase_all_remote() {
# =============================================================================
show_help() {
cat <<'EOF'
cat << 'EOF'
Nextcloud on Raspberry Pi 5 Setup Script
Usage: ./setup_nextcloud_raspberry.sh <command>

View File

@ -26,7 +26,7 @@ NC='\033[0m' # No Color
CHECK_ONLY=false
usage() {
cat <<EOF
cat << EOF
fix_anki.sh - Fix Anki startup issues
Usage: $(basename "$0") [OPTIONS]
@ -48,11 +48,11 @@ log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
check_anki_installed() {
if pacman -Qi anki-git &>/dev/null; then
if pacman -Qi anki-git &> /dev/null; then
echo "anki-git"
elif pacman -Qi anki &>/dev/null; then
elif pacman -Qi anki &> /dev/null; then
echo "anki"
elif pacman -Qi anki-bin &>/dev/null; then
elif pacman -Qi anki-bin &> /dev/null; then
echo "anki-bin"
else
echo ""
@ -66,7 +66,7 @@ get_system_python_version() {
get_anki_python_version() {
local anki_pkg="$1"
local anki_path
anki_path=$(pacman -Ql "$anki_pkg" 2>/dev/null | grep -oP '/usr/lib/python\K[0-9]+\.[0-9]+' | head -1)
anki_path=$(pacman -Ql "$anki_pkg" 2> /dev/null | grep -oP '/usr/lib/python\K[0-9]+\.[0-9]+' | head -1)
echo "$anki_path"
}
@ -74,10 +74,10 @@ check_aqt_conflict() {
local sys_python="$1"
local aqt_path="/usr/lib/python${sys_python}/site-packages/aqt/__init__.py"
if [[ -f "$aqt_path" ]]; then
if grep -q "aqtinstall" "$aqt_path" 2>/dev/null; then
if [[ -f $aqt_path ]]; then
if grep -q "aqtinstall" "$aqt_path" 2> /dev/null; then
echo "aqtinstall"
elif grep -q "anki" "$aqt_path" 2>/dev/null; then
elif grep -q "anki" "$aqt_path" 2> /dev/null; then
echo "anki"
else
echo "unknown"
@ -112,7 +112,7 @@ main() {
# Check which Anki package is installed
local anki_pkg
anki_pkg=$(check_anki_installed)
if [[ -z "$anki_pkg" ]]; then
if [[ -z $anki_pkg ]]; then
log_error "Anki is not installed"
exit 1
fi
@ -129,7 +129,7 @@ main() {
local issues_found=false
# Check for Python version mismatch
if [[ -n "$anki_python" && "$sys_python" != "$anki_python" ]]; then
if [[ -n $anki_python && $sys_python != "$anki_python" ]]; then
log_warn "Python version mismatch detected!"
log_warn " Anki was built for Python $anki_python but system runs Python $sys_python"
issues_found=true
@ -149,7 +149,7 @@ main() {
log_success "aqt module belongs to Anki (correct)"
;;
none)
if [[ "$sys_python" != "$anki_python" ]]; then
if [[ $sys_python != "$anki_python" ]]; then
log_warn "No aqt module found for Python $sys_python"
fi
;;
@ -160,9 +160,9 @@ main() {
# Test if Anki actually works
log_info "Testing Anki startup..."
if python -c "from aqt import run" 2>/dev/null; then
if python -c "from aqt import run" 2> /dev/null; then
log_success "Anki imports work correctly"
if [[ "$issues_found" == "false" ]]; then
if [[ $issues_found == "false" ]]; then
log_success "No issues found with Anki installation"
exit 0
fi
@ -171,8 +171,8 @@ main() {
issues_found=true
fi
if [[ "$CHECK_ONLY" == "true" ]]; then
if [[ "$issues_found" == "true" ]]; then
if [[ $CHECK_ONLY == "true" ]]; then
if [[ $issues_found == "true" ]]; then
echo ""
log_info "Issues detected. Run without --check to fix."
exit 1
@ -185,10 +185,10 @@ main() {
log_info "Applying fixes..."
# Check if python-aqtinstall is installed and remove it if nothing depends on it
if pacman -Qi python-aqtinstall &>/dev/null; then
if pacman -Qi python-aqtinstall &> /dev/null; then
local required_by
required_by=$(pacman -Qi python-aqtinstall | grep "Required By" | cut -d: -f2 | xargs)
if [[ "$required_by" == "None" ]]; then
if [[ $required_by == "None" ]]; then
log_info "Removing python-aqtinstall (conflicts with Anki)..."
sudo pacman -R --noconfirm python-aqtinstall
else
@ -198,10 +198,10 @@ main() {
fi
# Rebuild anki package
if [[ "$anki_pkg" == "anki-git" ]]; then
if [[ $anki_pkg == "anki-git" ]]; then
log_info "Rebuilding anki-git for Python $sys_python..."
yay -S anki-git --rebuild --noconfirm
elif [[ "$anki_pkg" == "anki" ]]; then
elif [[ $anki_pkg == "anki" ]]; then
log_info "Reinstalling anki..."
sudo pacman -S anki --noconfirm
else
@ -211,7 +211,7 @@ main() {
# Verify fix
echo ""
log_info "Verifying fix..."
if python -c "from aqt import run" 2>/dev/null; then
if python -c "from aqt import run" 2> /dev/null; then
log_success "Anki is now working!"
echo ""
echo "You can start Anki with: anki"

View File

@ -33,10 +33,10 @@ if [[ ! -f $ORGANIZE_SCRIPT ]]; then
fi
# Stop the service if running (ignore errors)
systemctl stop "$SERVICE_NAME.service" 2>/dev/null || true
systemctl stop "$SERVICE_NAME.service" 2> /dev/null || true
# Recreate the service file with correct configuration
cat >"$SERVICE_FILE" <<EOF
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=Media File Organizer
After=graphical-session.target
@ -62,7 +62,7 @@ systemctl daemon-reload
log "Reloaded systemd daemon"
# Reset the failed state
systemctl reset-failed "$SERVICE_NAME.service" 2>/dev/null || true
systemctl reset-failed "$SERVICE_NAME.service" 2> /dev/null || true
log "Reset failed state"
# Re-enable the service

View File

@ -32,7 +32,7 @@ YELLOW='\033[1;33m'
NC='\033[0m' # No Color
usage() {
cat <<EOF
cat << EOF
fix_thorium.sh - Fix Thorium Browser crashes and startup issues
Usage: $(basename "$0") [OPTIONS]
@ -87,17 +87,17 @@ done
# Check if Thorium is installed
check_thorium_installed() {
if ! command -v thorium-browser &>/dev/null; then
if ! command -v thorium-browser &> /dev/null; then
log_error "thorium-browser not found in PATH"
echo -e "${YELLOW}Install with: yay -S thorium-browser-bin${NC}"
exit 1
fi
log_info "Found Thorium: $(thorium-browser --version 2>/dev/null | head -1)"
log_info "Found Thorium: $(thorium-browser --version 2> /dev/null | head -1)"
}
# Check if config directory exists
check_config_exists() {
if [[ ! -d "$THORIUM_CONFIG_DIR" ]]; then
if [[ ! -d $THORIUM_CONFIG_DIR ]]; then
log_warn "Thorium config directory not found: $THORIUM_CONFIG_DIR"
log_info "This may be a fresh install - try running thorium-browser directly"
exit 0
@ -107,7 +107,7 @@ check_config_exists() {
# Kill any running Thorium processes
kill_thorium() {
local count
count=$(pgrep -c thorium 2>/dev/null || true)
count=$(pgrep -c thorium 2> /dev/null || true)
count=${count:-0}
if [[ $count -gt 0 ]]; then
@ -115,7 +115,7 @@ kill_thorium() {
if [[ $DRY_RUN == true ]]; then
echo " [dry-run] Would kill thorium processes"
else
pkill -9 thorium 2>/dev/null || true
pkill -9 thorium 2> /dev/null || true
sleep 1
fi
fi
@ -127,7 +127,7 @@ backup_if_exists() {
local name
name=$(basename "$path")
if [[ -e "$path" ]]; then
if [[ -e $path ]]; then
local backup_path="${path}${BACKUP_SUFFIX}"
if [[ $DRY_RUN == true ]]; then
echo " [dry-run] Would backup: $name"
@ -146,7 +146,7 @@ remove_if_exists() {
local name
name=$(basename "$path")
if [[ -e "$path" ]]; then
if [[ -e $path ]]; then
if [[ $DRY_RUN == true ]]; then
echo " [dry-run] Would remove: $name"
else
@ -163,9 +163,9 @@ fix_local_state() {
log_info "Checking Local State file..."
local local_state="$THORIUM_CONFIG_DIR/Local State"
if [[ -f "$local_state" ]]; then
if [[ -f $local_state ]]; then
# Check if it's valid JSON
if ! python3 -c "import json; json.load(open('$local_state'))" 2>/dev/null; then
if ! python3 -c "import json; json.load(open('$local_state'))" 2> /dev/null; then
log_warn "Local State file appears corrupted"
backup_if_exists "$local_state"
else
@ -226,9 +226,9 @@ fix_crash_reports() {
log_info "Clearing old crash reports..."
local crash_dir="$THORIUM_CONFIG_DIR/Crash Reports"
if [[ -d "$crash_dir" ]]; then
if [[ -d $crash_dir ]]; then
local crash_count
crash_count=$(find "$crash_dir" -type f 2>/dev/null | wc -l)
crash_count=$(find "$crash_dir" -type f 2> /dev/null | wc -l)
if [[ $crash_count -gt 0 ]]; then
if [[ $DRY_RUN == true ]]; then
echo " [dry-run] Would clear $crash_count crash report(s)"
@ -268,11 +268,11 @@ fix_aggressive() {
)
for db in "${db_files[@]}"; do
if [[ -f "$db" ]]; then
if [[ -f $db ]]; then
log_info "Checking database: $(basename "$db")"
# Simple corruption check - if sqlite3 can't open it, back it up
if command -v sqlite3 &>/dev/null; then
if ! sqlite3 "$db" "PRAGMA integrity_check;" &>/dev/null; then
if command -v sqlite3 &> /dev/null; then
if ! sqlite3 "$db" "PRAGMA integrity_check;" &> /dev/null; then
log_warn "Database may be corrupted: $(basename "$db")"
backup_if_exists "$db"
fi
@ -295,13 +295,13 @@ test_thorium() {
fi
# Start Thorium in background
thorium-browser &>/dev/null &
thorium-browser &> /dev/null &
local pid=$!
# Wait a few seconds and check if it's still running
sleep 4
if kill -0 "$pid" 2>/dev/null; then
if kill -0 "$pid" 2> /dev/null; then
log_ok "Thorium started successfully! (PID: $pid)"
echo -e "${GREEN}Fix successful!${NC} Thorium is now running."
@ -309,7 +309,7 @@ test_thorium() {
read -r -p "Keep browser running? [Y/n] " response
case "$response" in
[nN]*)
kill "$pid" 2>/dev/null || true
kill "$pid" 2> /dev/null || true
log_info "Browser closed"
;;
*)

View File

@ -44,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
@ -59,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
@ -88,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
@ -109,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
@ -124,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

View File

@ -8,13 +8,13 @@ set -euo pipefail
echo "=== Fixing yay AUR database ==="
# Check if using yay-git (development version with potential bugs)
if pacman -Qi yay-git &>/dev/null; then
if pacman -Qi yay-git &> /dev/null; then
echo ""
echo "Detected yay-git (development version)."
echo "The 'database AUR not found' error is a known bug in some yay-git versions."
echo ""
read -rp "Switch to stable yay? [Y/n] " response
if [[ "${response,,}" != "n" ]]; then
if [[ ${response,,} != "n" ]]; then
echo "Switching to stable yay..."
# Build and install stable yay from AUR
@ -25,7 +25,7 @@ if pacman -Qi yay-git &>/dev/null; then
# Remove yay-git and yay-git-debug (they conflict)
sudo pacman -Rdd yay-git --noconfirm
sudo pacman -Rdd yay-git-debug --noconfirm 2>/dev/null || true
sudo pacman -Rdd yay-git-debug --noconfirm 2> /dev/null || true
# Build and install stable yay
makepkg -si --noconfirm
@ -42,21 +42,21 @@ fi
# Remove yay's cache directory
YAY_CACHE_DIR="${HOME}/.cache/yay"
if [[ -d "$YAY_CACHE_DIR" ]]; then
if [[ -d $YAY_CACHE_DIR ]]; then
echo "Removing yay cache directory: $YAY_CACHE_DIR"
rm -rf "$YAY_CACHE_DIR"
fi
# Remove yay's local database directory (stores AUR package info)
YAY_DB_DIR="${HOME}/.local/share/yay"
if [[ -d "$YAY_DB_DIR" ]]; then
if [[ -d $YAY_DB_DIR ]]; then
echo "Removing yay database directory: $YAY_DB_DIR"
rm -rf "$YAY_DB_DIR"
fi
# Remove yay state directory
YAY_STATE_DIR="${HOME}/.local/state/yay"
if [[ -d "$YAY_STATE_DIR" ]]; then
if [[ -d $YAY_STATE_DIR ]]; then
echo "Removing yay state directory: $YAY_STATE_DIR"
rm -rf "$YAY_STATE_DIR"
fi

View File

@ -34,7 +34,7 @@ echo "======================================"
mkdir -p "$MODPROBE_DIR"
# Create the configuration file
cat >"$CONFIG_FILE" <<EOF
cat > "$CONFIG_FILE" << EOF
# Disable NVIDIA GSP firmware to prevent Vulkan failures and crashes
# Created by nvidia_troubleshoot.sh on $(date)
options nvidia NVreg_EnableGpuFirmware=0
@ -69,7 +69,7 @@ configure_xorg() {
backup_file "$NVIDIA_CONF"
# Create NVIDIA-specific configuration
cat >"$NVIDIA_CONF" <<EOF
cat > "$NVIDIA_CONF" << EOF
# NVIDIA configuration with RenderAccel disabled
# Created by nvidia_troubleshoot.sh on $(date)
Section "Device"
@ -100,7 +100,7 @@ configure_gcc_workaround() {
printf '# NVIDIA GCC version mismatch workaround\n'
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
printf 'export IGNORE_CC_MISMATCH=1\n'
} >>"$PROFILE_FILE"
} >> "$PROFILE_FILE"
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
else
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
@ -137,7 +137,7 @@ install_pyroveil() {
local missing_deps=()
for dep in git cmake ninja gcc; do
if ! command -v "$dep" &>/dev/null; then
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
@ -178,7 +178,7 @@ install_pyroveil() {
echo "Available configs in: $pyroveil_dir/hacks/"
# Create a helper script
cat >"$user_home/run-with-pyroveil.sh" <<EOF
cat > "$user_home/run-with-pyroveil.sh" << EOF
#!/bin/bash
# Helper script to run games with Pyroveil
# Usage: ./run-with-pyroveil.sh <config-name> <command>
@ -244,7 +244,7 @@ suggest_kernel_params() {
# Check current CPU for micro-op cache relevance
echo ""
echo "CPU Information (for micro-op cache consideration):"
if command -v lscpu &>/dev/null; then
if command -v lscpu &> /dev/null; then
local cpu_info
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
echo "Current CPU: $cpu_info"
@ -297,10 +297,10 @@ install_pyroveil
echo ""
echo "7. Regenerating Initramfs..."
echo "============================"
if command -v mkinitcpio &>/dev/null; then
if command -v mkinitcpio &> /dev/null; then
mkinitcpio -P
echo "✓ Initramfs regenerated with mkinitcpio"
elif command -v dracut &>/dev/null; then
elif command -v dracut &> /dev/null; then
dracut --force
echo "✓ Initramfs regenerated with dracut"
else

View File

@ -45,7 +45,7 @@ check_adb_device() {
# 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
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"

View File

@ -22,7 +22,7 @@ log_message() {
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
echo "$formatted" >&2
if [[ -n $log_file ]]; then
echo "$formatted" >>"$log_file" 2>/dev/null || true
echo "$formatted" >> "$log_file" 2> /dev/null || true
fi
}
@ -181,10 +181,10 @@ FOCUS_APPS_PROCESSES=(
# Echoes the name of the found app
is_focus_app_running() {
# Check windows first
if command -v xdotool &>/dev/null; then
if command -v xdotool &> /dev/null; then
local app
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
if xdotool search --name "$app" &>/dev/null 2>&1; then
if xdotool search --name "$app" &> /dev/null 2>&1; then
echo "$app"
return 0
fi
@ -194,7 +194,7 @@ is_focus_app_running() {
# Check specific processes
local app
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
if pgrep -f "$app" &>/dev/null; then
if pgrep -f "$app" &> /dev/null; then
echo "$app"
return 0
fi
@ -212,7 +212,7 @@ is_focus_app_running() {
require_command() {
local cmd="$1"
local pkg="${2:-$1}"
if ! command -v "$cmd" >/dev/null 2>&1; then
if ! command -v "$cmd" > /dev/null 2>&1; then
echo "Error: '$cmd' is not installed or not in PATH." >&2
echo "Install with: sudo pacman -S $pkg" >&2
return 1
@ -227,7 +227,7 @@ require_imagemagick() {
local preferred="${1:-}"
if [[ $preferred == "magick" ]] || [[ -z $preferred ]]; then
if command -v magick &>/dev/null; then
if command -v magick &> /dev/null; then
MAGICK_CMD="magick"
export MAGICK_CMD
return 0
@ -235,7 +235,7 @@ require_imagemagick() {
fi
if [[ $preferred == "convert" ]] || [[ -z $preferred ]]; then
if command -v convert &>/dev/null; then
if command -v convert &> /dev/null; then
MAGICK_CMD="convert"
export MAGICK_CMD
return 0
@ -257,7 +257,7 @@ install_missing_pacman_packages() {
local missing=()
for pkg in "${packages[@]}"; do
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
if ! pacman -Qi "$pkg" > /dev/null 2>&1; then
missing+=("$pkg")
fi
done
@ -287,8 +287,8 @@ notify() {
local urgency="${3:-normal}"
local timeout="${4:-5000}"
if command -v notify-send &>/dev/null; then
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true
if command -v notify-send &> /dev/null; then
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2> /dev/null || true
fi
}
@ -344,7 +344,7 @@ is_service_active() {
# 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
_systemctl_cmd "${2:-}" is-enabled --quiet "$1" 2> /dev/null
}
# =============================================================================
@ -397,7 +397,7 @@ ask_yes_no() {
# Check if a command is available
# Usage: if has_cmd git; then ...
has_cmd() {
command -v "$1" >/dev/null 2>&1
command -v "$1" > /dev/null 2>&1
}
# =============================================================================
@ -429,7 +429,7 @@ print_setup_header() {
# 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
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
@ -441,7 +441,7 @@ collapse_mounts() {
if has_cmd mountpoint; then
while mountpoint -q "$target"; do
umount -l "$target" >/dev/null 2>&1 || break
umount -l "$target" > /dev/null 2>&1 || break
i=$((i + 1))
((i >= max_iter)) && break
done
@ -449,7 +449,7 @@ collapse_mounts() {
local cnt
cnt=$(mount_layers_count "$target")
while ((cnt > 1)); do
umount -l "$target" >/dev/null 2>&1 || break
umount -l "$target" > /dev/null 2>&1 || break
i=$((i + 1))
((i >= max_iter)) && break
cnt=$(mount_layers_count "$target")

View File

@ -31,7 +31,7 @@ LIST_ONLY="false"
VERBOSE="false"
usage() {
cat <<EOF
cat << EOF
Usage: $(basename "$0") [options]
Options:
@ -88,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; }
@ -131,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
@ -177,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
@ -210,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
@ -225,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
@ -266,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
@ -284,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
@ -292,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
@ -318,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
@ -329,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

View File

@ -28,7 +28,7 @@ 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:
@ -70,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
@ -89,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 },
@ -111,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
@ -122,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
@ -131,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."
@ -147,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.

View File

@ -30,7 +30,7 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "$SCRIPT_DIR/../../lib/common.sh"
usage() {
cat <<EOF
cat << EOF
${SCRIPT_NAME} - Fix Unity Hub sign-in by registering unityhub:// URL handler
Options:
@ -69,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
}
@ -112,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"
@ -122,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"
@ -145,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
@ -198,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
@ -218,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
@ -238,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
@ -252,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."
@ -286,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.

View File

@ -62,9 +62,9 @@ try_download_model() {
tmp=$(mktemp)
echo "Attempting to download RNNoise model from: $url" >&2
if has_cmd curl; then
curl -fsSL "$url" -o "$tmp" 2>/dev/null || true
curl -fsSL "$url" -o "$tmp" 2> /dev/null || true
else
wget -qO "$tmp" "$url" 2>/dev/null || true
wget -qO "$tmp" "$url" 2> /dev/null || true
fi
if [[ -s $tmp ]]; then
mv "$tmp" "$dest"
@ -145,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

View File

@ -31,7 +31,7 @@ ensure_pacman_packages() {
}
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
@ -42,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
@ -120,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
@ -140,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."
@ -151,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
@ -166,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.

View File

@ -65,7 +65,7 @@ err() { echo -e "${RED}[ERR ]${RESET} $*" >&2; }
success() { echo -e "${GREEN}[OK ]${RESET} $*"; }
usage() {
cat <<EOF
cat << EOF
${SCRIPT_NAME} v${VERSION}
Setup or uninstall a self-hosted LibreTranslate instance via Docker.
@ -116,7 +116,7 @@ gen_api_key() {
key=$(head -c 256 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 40 || true)
if [[ -z $key || ${#key} -lt 40 ]]; then
# Fallback using openssl if available
if command -v openssl >/dev/null 2>&1; then
if command -v openssl > /dev/null 2>&1; then
key=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40 || true)
fi
fi
@ -128,7 +128,7 @@ gen_api_key() {
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
command -v "$1" > /dev/null 2>&1 || {
err "Required command '$1' not found"
return 1
}
@ -250,7 +250,7 @@ ensure_root() {
}
install_docker() {
if command -v docker >/dev/null 2>&1; then
if command -v docker > /dev/null 2>&1; then
log "Docker already installed"
return 0
fi
@ -259,7 +259,7 @@ install_docker() {
exit 1
fi
log "Installing Docker..."
if command -v apt-get >/dev/null 2>&1; then
if command -v apt-get > /dev/null 2>&1; then
apt-get update -y
apt-get install -y ca-certificates curl gnupg
install -d -m 0755 /etc/apt/keyrings
@ -276,7 +276,7 @@ install_docker() {
. /etc/os-release
echo "$VERSION_CODENAME"
) stable" \
>/etc/apt/sources.list.d/docker.list
> /etc/apt/sources.list.d/docker.list
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
else
@ -284,8 +284,8 @@ install_docker() {
exit 1
fi
# Attempt to start docker daemon if dockerd exists and systemctl available; otherwise rely on user
if command -v systemctl >/dev/null 2>&1; then
(systemctl enable --now docker 2>/dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
if command -v systemctl > /dev/null 2>&1; then
(systemctl enable --now docker 2> /dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
else
warn "Docker installed; please ensure docker daemon is running"
fi
@ -299,12 +299,12 @@ pull_image() {
detect_container_user() {
# Determine uid/gid of configured user inside image so host dirs can be chowned
if ! command -v docker >/dev/null 2>&1; then
if ! command -v docker > /dev/null 2>&1; then
return 0
fi
local uid gid
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 2>/dev/null || echo "")
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2>/dev/null || echo "")
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 2> /dev/null || echo "")
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2> /dev/null || echo "")
if [[ -n $uid && -n $gid ]]; then
CONTAINER_UID=$uid
CONTAINER_GID=$gid
@ -315,12 +315,12 @@ write_env_file() {
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${CACHE_DIR}"
detect_container_user
if [[ -n ${CONTAINER_UID:-} && -n ${CONTAINER_GID:-} ]]; then
if command -v stat >/dev/null 2>&1; then
if command -v stat > /dev/null 2>&1; then
for d in "${DATA_DIR}" "${CACHE_DIR}"; do
if [[ -d $d ]]; then
CUR_UID=$(stat -c %u "$d" 2>/dev/null || echo -1)
CUR_UID=$(stat -c %u "$d" 2> /dev/null || echo -1)
if [[ ${CUR_UID} -ne ${CONTAINER_UID} ]]; then
chown "${CONTAINER_UID}":"${CONTAINER_GID}" "$d" 2>/dev/null || warn "Unable to chown $d to ${CONTAINER_UID}:${CONTAINER_GID}"
chown "${CONTAINER_UID}":"${CONTAINER_GID}" "$d" 2> /dev/null || warn "Unable to chown $d to ${CONTAINER_UID}:${CONTAINER_GID}"
fi
fi
done
@ -344,14 +344,14 @@ write_env_file() {
echo "${API_KEY_LINE}"
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}"
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done
} >"${ENV_FILE}.tmp"
} > "${ENV_FILE}.tmp"
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
chmod 600 "${ENV_FILE}"
success "Environment file written: ${ENV_FILE}"
}
start_container_ephemeral() {
docker rm -f "${SERVICE_NAME}" >/dev/null 2>&1 || true
docker rm -f "${SERVICE_NAME}" > /dev/null 2>&1 || true
docker run -d --name "${SERVICE_NAME}" \
--env-file "${ENV_FILE}" \
-v "${DATA_DIR}:/home/libretranslate/.local/share/argos-translate" \
@ -372,7 +372,7 @@ health_check() {
local attempt=0
while true; do
attempt=$((attempt + 1))
if curl ${DEBUG:+-v} -fsS "$url" >/dev/null 2>&1; then
if curl ${DEBUG:+-v} -fsS "$url" > /dev/null 2>&1; then
success "Service healthy (attempt $attempt)"
return 0
else
@ -405,8 +405,8 @@ sample_request() {
uninstall_all() {
log "Uninstalling LibreTranslate (ephemeral mode)..."
docker rm -f "${SERVICE_NAME}" 2>/dev/null || true
docker rmi "${IMAGE}:${TAG}" 2>/dev/null || true
docker rm -f "${SERVICE_NAME}" 2> /dev/null || true
docker rmi "${IMAGE}:${TAG}" 2> /dev/null || true
if [[ ${KEEP_DATA} -eq 0 ]]; then
rm -rf "${DATA_DIR}" "${CONFIG_DIR}" || true
success "Data directories removed"
@ -448,7 +448,7 @@ main() {
CMD_STATUS=$?
set -e
log "Command exited with status ${CMD_STATUS}; stopping container"
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
docker stop "${SERVICE_NAME}" > /dev/null 2>&1 || true
exit ${CMD_STATUS}
fi
@ -457,9 +457,9 @@ main() {
trap 'log "Stopping container"; docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true; exit 0' INT TERM
docker logs -f "${SERVICE_NAME}"
log "Logs ended; stopping container"
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
docker stop "${SERVICE_NAME}" > /dev/null 2>&1 || true
else
log "Ephemeral container left running in background (id: $(docker inspect --format '{{.Id}}' ${SERVICE_NAME} 2>/dev/null || echo unknown))"
log "Ephemeral container left running in background (id: $(docker inspect --format '{{.Id}}' ${SERVICE_NAME} 2> /dev/null || echo unknown))"
log "Stop manually with: docker stop ${SERVICE_NAME}"
fi

View File

@ -15,7 +15,7 @@ PY_HELPERS="$TOOLS_DIR/transcribe_helpers.py"
VENV_DIR="$PROJECT_DIR/.venv"
usage() {
cat <<USAGE
cat << USAGE
Usage: $(basename "$0") [--online] [--prepare-model NAME --model-dir DIR] [-m model] [-l lang] [-o outdir] [audio_file]
Options:
@ -35,23 +35,23 @@ log() {
}
detect_pkg_mgr() {
if command -v apt-get >/dev/null 2>&1; then
if command -v apt-get > /dev/null 2>&1; then
echo apt
return
fi
if command -v dnf >/dev/null 2>&1; then
if command -v dnf > /dev/null 2>&1; then
echo dnf
return
fi
if command -v yum >/dev/null 2>&1; then
if command -v yum > /dev/null 2>&1; then
echo yum
return
fi
if command -v pacman >/dev/null 2>&1; then
if command -v pacman > /dev/null 2>&1; then
echo pacman
return
fi
if command -v zypper >/dev/null 2>&1; then
if command -v zypper > /dev/null 2>&1; then
echo zypper
return
fi
@ -74,7 +74,7 @@ has_libcublas12() {
# venv-provided NVIDIA CUDA libs
if [[ -x "$VENV_DIR/bin/python" ]]; then
local pyver
pyver="$("$VENV_DIR"/bin/python "$PY_HELPERS" python-version 2>/dev/null || true)"
pyver="$("$VENV_DIR"/bin/python "$PY_HELPERS" python-version 2> /dev/null || true)"
if [[ -n $pyver ]]; then
for d in "$VENV_DIR/lib/python$pyver/site-packages/nvidia/cublas/lib" \
"$VENV_DIR/lib/python$pyver/site-packages/nvidia/cudnn/lib" \
@ -99,7 +99,7 @@ ensure_cuda_runtime() {
if has_libcublas12; then
return 0
fi
if ! command -v sudo >/dev/null 2>&1; then
if ! command -v sudo > /dev/null 2>&1; then
log "sudo not found; skipping CUDA runtime install attempt."
else
log "CUDA cuBLAS 12 not found; attempting to install CUDA runtime (manager: $mgr)"
@ -130,7 +130,7 @@ ensure_cuda_runtime() {
}
install_system_deps() {
have_cmd() { command -v "$1" >/dev/null 2>&1; }
have_cmd() { command -v "$1" > /dev/null 2>&1; }
local need_ffmpeg=0 need_espeak=0
have_cmd ffmpeg || need_ffmpeg=1
have_cmd espeak-ng || need_espeak=1
@ -158,7 +158,7 @@ install_system_deps() {
mgr="$(detect_pkg_mgr)"
log "Detected package manager: $mgr (installing missing: $([[ $need_ffmpeg -eq 1 ]] && echo ffmpeg)$([[ $need_espeak -eq 1 ]] && echo espeak-ng)$([[ $need_libsndfile -eq 1 ]] && echo libsndfile))"
if ! command -v sudo >/dev/null 2>&1; then
if ! command -v sudo > /dev/null 2>&1; then
log "sudo not found; skipping system package installation attempt."
return 0
fi
@ -246,7 +246,7 @@ install_python_deps() {
fi
if [[ $has_nvidia_flag -eq 1 ]]; then
# If ctranslate2 is not installed, attempt CUDA-enabled wheel (with fallback)
if ! "$VENV_DIR/bin/python" "$PY_HELPERS" check-ctranslate2 2>/dev/null; then
if ! "$VENV_DIR/bin/python" "$PY_HELPERS" check-ctranslate2 2> /dev/null; then
log "Installing CUDA-enabled CTranslate2 (cu12 wheel)"
python -m pip install --progress-bar on --retries 1 --upgrade "ctranslate2<5,>=4.0" --extra-index-url https://download.opennmt.net/ctranslate2/cu12 ||
log "Warning: could not reach cu12 wheel index; will proceed with available ctranslate2"
@ -278,14 +278,14 @@ ensure_runner() {
generate_test_audio() {
local tmpwav
tmpwav="${PROJECT_DIR}/test_fw.wav"
if command -v espeak-ng >/dev/null 2>&1; then
if command -v espeak-ng > /dev/null 2>&1; then
log "Generating test audio via espeak-ng -> $tmpwav" >&2
espeak-ng -w "$tmpwav" "This is a quick test of faster whisper transcription." >/dev/null 2>&1 || true
espeak-ng -w "$tmpwav" "This is a quick test of faster whisper transcription." > /dev/null 2>&1 || true
fi
# If espeak-ng failed or not present, try espeak
if [[ ! -s $tmpwav ]] && command -v espeak >/dev/null 2>&1; then
if [[ ! -s $tmpwav ]] && command -v espeak > /dev/null 2>&1; then
log "espeak-ng unavailable or failed; trying espeak -> $tmpwav" >&2
espeak -w "$tmpwav" "This is a quick test of faster whisper transcription." >/dev/null 2>&1 || true
espeak -w "$tmpwav" "This is a quick test of faster whisper transcription." > /dev/null 2>&1 || true
fi
# Fallback: generate tone via Python stdlib (no external deps)
if [[ ! -s $tmpwav ]]; then
@ -295,7 +295,7 @@ generate_test_audio() {
# Final fallback: tone via ffmpeg
if [[ ! -s $tmpwav ]]; then
log "Creating a 3s sine tone WAV via ffmpeg -> $tmpwav" >&2
ffmpeg -f lavfi -i sine=frequency=1000:duration=3 -ar 16000 -ac 1 -f wav -y "$tmpwav" >/dev/null 2>&1 || true
ffmpeg -f lavfi -i sine=frequency=1000:duration=3 -ar 16000 -ac 1 -f wav -y "$tmpwav" > /dev/null 2>&1 || true
fi
echo "$tmpwav"
}
@ -388,7 +388,7 @@ main() {
# Detect NVIDIA GPU and enforce CUDA if present
has_nvidia=0
if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then
if command -v nvidia-smi > /dev/null 2>&1 && nvidia-smi -L > /dev/null 2>&1; then
has_nvidia=1
fi
install_python_deps "$has_nvidia"
@ -427,7 +427,7 @@ main() {
# Include system and possible venv-provided CUDA libs
local pyver venv_cuda_paths=""
if [[ -x "$VENV_DIR/bin/python" ]]; then
pyver="$("$VENV_DIR"/bin/python "$PY_HELPERS" python-version 2>/dev/null || true)"
pyver="$("$VENV_DIR"/bin/python "$PY_HELPERS" python-version 2> /dev/null || true)"
if [[ -n $pyver ]]; then
venv_cuda_paths="$VENV_DIR/lib/python$pyver/site-packages/nvidia/cublas/lib:$VENV_DIR/lib/python$pyver/site-packages/nvidia/cudnn/lib:$VENV_DIR/lib/python$pyver/site-packages/nvidia/cuda_runtime/lib"
fi

View File

@ -101,7 +101,7 @@ create_execution_script() {
sed \
-e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \
-e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_MAINT_SCRIPT" >"$exec_script"
"$TEMPLATE_MAINT_SCRIPT" > "$exec_script"
chmod +x "$exec_script"
echo "✓ Installed execution script from template: $exec_script"
@ -151,7 +151,7 @@ create_hosts_monitor_service() {
# Install the monitor script from template with substitution
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_HOSTS_MONITOR" >"$monitor_script"
"$TEMPLATE_HOSTS_MONITOR" > "$monitor_script"
chmod +x "$monitor_script"
echo "✓ Installed hosts monitor script from template: $monitor_script"
@ -168,17 +168,17 @@ install_browser_preexec_wrapper() {
local wrapper="/usr/local/bin/browser-preexec-wrapper"
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_BROWSER_WRAPPER" >"$wrapper"
"$TEMPLATE_BROWSER_WRAPPER" > "$wrapper"
chmod +x "$wrapper"
echo "✓ Installed wrapper: $wrapper"
# Allow passwordless execution of hosts installer for root-only actions
local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd"
if command -v visudo >/dev/null 2>&1; then
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" >"$sudoers_file"
if command -v visudo > /dev/null 2>&1; then
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file"
chmod 440 "$sudoers_file"
# Validate syntax
visudo -c >/dev/null || echo "Warning: sudoers validation returned non-zero"
visudo -c > /dev/null || echo "Warning: sudoers validation returned non-zero"
echo "✓ Sudoers drop-in created: $sudoers_file"
else
echo "visudo not found; skipping sudoers drop-in"

View File

@ -28,7 +28,7 @@ check_thorium_browser() {
echo "1. Checking Thorium Browser Installation..."
echo "=========================================="
if ! command -v "$BROWSER_COMMAND" &>/dev/null; then
if ! command -v "$BROWSER_COMMAND" &> /dev/null; then
echo "Warning: Thorium browser not found in PATH"
echo "Checking alternative locations..."
@ -89,7 +89,7 @@ create_launcher_script() {
local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh"
cat >"$launcher_script" <<EOF
cat > "$launcher_script" << EOF
#!/bin/bash
# Thorium browser launcher for Fitatu website
# Created by setup_thorium_startup.sh on $(date)
@ -180,7 +180,7 @@ create_user_systemd_service() {
sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir"
# Create the service file
sudo -u "${SUDO_USER}" tee "$service_file" >/dev/null <<EOF
sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF
[Unit]
Description=Launch Thorium Browser with Fitatu on Startup
After=graphical-session.target
@ -216,7 +216,7 @@ create_system_systemd_service() {
local service_file="/etc/systemd/system/thorium-fitatu-startup.service"
cat >"$service_file" <<EOF
cat > "$service_file" << EOF
[Unit]
Description=Launch Thorium Browser with Fitatu on Startup
After=multi-user.target network-online.target
@ -259,7 +259,7 @@ create_autostart_entry() {
sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir"
# Create desktop entry
sudo -u "${SUDO_USER}" tee "$desktop_file" >/dev/null <<EOF
sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF
[Desktop Entry]
Type=Application
Name=Thorium Fitatu Startup
@ -312,7 +312,7 @@ create_i3_autostart() {
create_user_enable_script() {
local enable_script="$USER_HOME/.config/thorium-enable-service.sh"
sudo -u "${SUDO_USER}" tee "$enable_script" >/dev/null <<'EOF'
sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF'
#!/bin/bash
# Script to enable thorium-fitatu-startup user service
# This runs once to enable the service, then removes itself

View File

@ -23,18 +23,18 @@ needs_restoration() {
# Check if file is empty or too small (less than 1000 lines indicates tampering)
local line_count
line_count=$(wc -l <"$HOSTS_FILE" 2>/dev/null || echo "0")
line_count=$(wc -l < "$HOSTS_FILE" 2> /dev/null || echo "0")
if [[ $line_count -lt 1000 ]]; then
return 0 # File too small, likely tampered with
fi
# Check if our custom entries are missing
if ! grep -q "Custom blocking entries" "$HOSTS_FILE" 2>/dev/null; then
if ! grep -q "Custom blocking entries" "$HOSTS_FILE" 2> /dev/null; then
return 0 # Our custom entries missing, needs restoration
fi
# Check if StevenBlack entries are missing
if ! grep -q "StevenBlack" "$HOSTS_FILE" 2>/dev/null; then
if ! grep -q "StevenBlack" "$HOSTS_FILE" 2> /dev/null; then
return 0 # StevenBlack entries missing, needs restoration
fi
@ -48,7 +48,7 @@ restore_hosts_file() {
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
log_message "Running hosts installation script: $HOSTS_INSTALL_SCRIPT"
if bash "$HOSTS_INSTALL_SCRIPT" >>"$LOG_FILE" 2>&1; then
if bash "$HOSTS_INSTALL_SCRIPT" >> "$LOG_FILE" 2>&1; then
log_message "Hosts file restoration completed successfully"
else
log_message "Hosts file restoration failed with exit code $?"
@ -63,7 +63,7 @@ monitor_with_inotify() {
log_message "Starting hosts file monitoring with inotify"
# Monitor the hosts file and its directory for various events
inotifywait -m -e delete,move,modify,attrib,create --format '%w%f %e %T' --timefmt '%Y-%m-%d %H:%M:%S' "$HOSTS_FILE" /etc/ 2>/dev/null |
inotifywait -m -e delete,move,modify,attrib,create --format '%w%f %e %T' --timefmt '%Y-%m-%d %H:%M:%S' "$HOSTS_FILE" /etc/ 2> /dev/null |
while read -r file event time; do
# Check if the event is related to our hosts file
if [[ $file == "$HOSTS_FILE" ]] || [[ $file == "/etc/hosts" ]]; then
@ -100,7 +100,7 @@ monitor_with_polling() {
log_message "=== Hosts File Monitor Started ==="
# Check if inotify-tools is available
if command -v inotifywait >/dev/null 2>&1; then
if command -v inotifywait > /dev/null 2>&1; then
log_message "Using inotify for file monitoring"
monitor_with_inotify
else

View File

@ -18,13 +18,13 @@ log_message() {
# Function to check if timer needs to be re-enabled
timer_needs_restoration() {
# Check if timer is enabled
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
if ! systemctl is-enabled "$TIMER_NAME" &> /dev/null; then
log_message "Timer $TIMER_NAME is not enabled"
return 0
fi
# Check if timer is active
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
if ! systemctl is-active "$TIMER_NAME" &> /dev/null; then
log_message "Timer $TIMER_NAME is not active"
return 0
fi
@ -58,19 +58,19 @@ restore_timer() {
systemctl daemon-reload
# Re-enable timer if disabled
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
if ! systemctl is-enabled "$TIMER_NAME" &> /dev/null; then
log_message "Re-enabling $TIMER_NAME"
systemctl enable "$TIMER_NAME" 2>/dev/null || true
systemctl enable "$TIMER_NAME" 2> /dev/null || true
fi
# Re-start timer if not active
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
if ! systemctl is-active "$TIMER_NAME" &> /dev/null; then
log_message "Re-starting $TIMER_NAME"
systemctl start "$TIMER_NAME" 2>/dev/null || true
systemctl start "$TIMER_NAME" 2> /dev/null || true
fi
# Verify restoration
if systemctl is-active "$TIMER_NAME" &>/dev/null; then
if systemctl is-active "$TIMER_NAME" &> /dev/null; then
log_message "Timer restoration completed successfully"
else
log_message "WARNING: Timer restoration may have failed"
@ -83,9 +83,9 @@ monitor_with_dbus() {
# Use busctl to monitor systemd unit changes
# Fall back to polling if this fails
if command -v busctl &>/dev/null; then
if command -v busctl &> /dev/null; then
# Monitor for unit state changes
busctl monitor --system org.freedesktop.systemd1 2>/dev/null |
busctl monitor --system org.freedesktop.systemd1 2> /dev/null |
while read -r line; do
# Check if the line mentions our timer
if echo "$line" | grep -q "$TIMER_NAME\|$SERVICE_NAME"; then

View File

@ -39,7 +39,7 @@ EXCLUDE_DIRS="node_modules|\.git|vendor|\.venv|venv|__pycache__|\.cache|build|di
# Detect if input is a URL or local path
is_url() {
[[ "$1" =~ ^https?:// ]] || [[ "$1" =~ ^git@ ]] || [[ "$1" =~ ^ssh:// ]]
[[ $1 =~ ^https?:// ]] || [[ $1 =~ ^git@ ]] || [[ $1 =~ ^ssh:// ]]
}
IS_LOCAL=false
@ -84,7 +84,7 @@ print_subheader() {
# Check if we're in a git repository
is_git_repo() {
git rev-parse --is-inside-work-tree &>/dev/null
git rev-parse --is-inside-work-tree &> /dev/null
}
# Helper function to find files while respecting exclusions
@ -102,8 +102,8 @@ find_files() {
done
# Get tracked files + untracked (but not ignored) files
{
git ls-files -- "${git_patterns[@]}" 2>/dev/null
git ls-files --others --exclude-standard -- "${git_patterns[@]}" 2>/dev/null
git ls-files -- "${git_patterns[@]}" 2> /dev/null
git ls-files --others --exclude-standard -- "${git_patterns[@]}" 2> /dev/null
} | sort -u
else
# Not a git repo - fall back to manual exclusion
@ -115,7 +115,7 @@ find_files() {
find_args+=(-o -name "${patterns[$i]}")
fi
done
find . -type f \( "${find_args[@]}" \) 2>/dev/null | grep -Ev "/($EXCLUDE_DIRS)/"
find . -type f \( "${find_args[@]}" \) 2> /dev/null | grep -Ev "/($EXCLUDE_DIRS)/"
fi
else
# No filtering - find all files
@ -127,7 +127,7 @@ find_files() {
find_args+=(-o -name "${patterns[$i]}")
fi
done
find . -type f \( "${find_args[@]}" \) 2>/dev/null
find . -type f \( "${find_args[@]}" \) 2> /dev/null
fi
}
@ -158,7 +158,7 @@ install_missing_tools() {
if ! command -v counts &> /dev/null; then
if command -v cargo &> /dev/null; then
echo "Installing 'counts' via cargo (fast word counter)..."
cargo install counts 2>/dev/null || echo "Warning: counts install failed, will use Python fallback"
cargo install counts 2> /dev/null || echo "Warning: counts install failed, will use Python fallback"
fi
fi
@ -231,7 +231,7 @@ install_missing_tools() {
elif command -v dnf &> /dev/null; then
# Fedora
echo "Installing tools via dnf..."
sudo dnf install -y "${MISSING_TOOLS[@]}" "${MISSING_AUR[@]}" 2>/dev/null || {
sudo dnf install -y "${MISSING_TOOLS[@]}" "${MISSING_AUR[@]}" 2> /dev/null || {
# tokei/scc might need cargo
for aur_tool in "${MISSING_AUR[@]}"; do
if command -v cargo &> /dev/null; then
@ -279,7 +279,7 @@ else
echo "Repository already exists at $REPO_DIR"
echo "Updating..."
cd "$REPO_DIR"
git pull --depth 1 2>/dev/null || echo "Update skipped (shallow clone)"
git pull --depth 1 2> /dev/null || echo "Update skipped (shallow clone)"
else
echo "Cloning $REPO_URL (shallow clone for speed)..."
git clone --depth 1 "$REPO_URL" "$REPO_DIR"
@ -292,10 +292,13 @@ echo "Location: $REPO_DIR"
echo "Repository size: $(du -sh . | cut -f1)"
if [ "$RESPECT_GITIGNORE" = true ] && is_git_repo; then
# Count files respecting .gitignore
FILE_COUNT=$({ git ls-files 2>/dev/null; git ls-files --others --exclude-standard 2>/dev/null; } | sort -u | wc -l)
FILE_COUNT=$({
git ls-files 2> /dev/null
git ls-files --others --exclude-standard 2> /dev/null
} | sort -u | wc -l)
echo "Files: $FILE_COUNT (respecting .gitignore)"
elif [ "$RESPECT_GITIGNORE" = true ]; then
echo "Files: $(find . -type f 2>/dev/null | grep -Ev "/($EXCLUDE_DIRS)/" | wc -l) (excluding common dirs)"
echo "Files: $(find . -type f 2> /dev/null | grep -Ev "/($EXCLUDE_DIRS)/" | wc -l) (excluding common dirs)"
else
echo "Files: $(find . -type f | wc -l)"
fi
@ -317,7 +320,7 @@ echo "Running scc..."
scc . | tee "$RESULTS_DIR/scc_stats.txt"
print_subheader "Top 10 Most Complex Files"
scc --by-file --sort complexity . 2>/dev/null | head -20 | tee "$RESULTS_DIR/scc_complexity.txt"
scc --by-file --sort complexity . 2> /dev/null | head -20 | tee "$RESULTS_DIR/scc_complexity.txt"
#==============================================================================
# STEP 4: Fast Keyword Analysis (Code vs Comments) - Multi-Language
@ -329,7 +332,7 @@ print_header "STEP 4: Fast Keyword Analysis (Code vs Comments)"
fast_count() {
local top_n="${1:-50}"
if command -v counts &> /dev/null; then
counts 2>/dev/null | head -$((top_n + 1)) | tail -$top_n
counts 2> /dev/null | head -$((top_n + 1)) | tail -$top_n
else
python3 -c "
import sys
@ -386,14 +389,14 @@ HAS_GO=false
HAS_RUST=false
HAS_JAVA=false
(( ${LANG_FILES[c]} + ${LANG_FILES[cpp]} + ${LANG_FILES[h]} > 0 )) && HAS_C_FAMILY=true
(( ${LANG_FILES[python]} > 0 )) && HAS_PYTHON=true
(( ${LANG_FILES[javascript]} + ${LANG_FILES[typescript]} > 0 )) && HAS_JS_FAMILY=true
(( ${LANG_FILES[shell]} > 0 )) && HAS_SHELL=true
(( ${LANG_FILES[ruby]} > 0 )) && HAS_RUBY=true
(( ${LANG_FILES[go]} > 0 )) && HAS_GO=true
(( ${LANG_FILES[rust]} > 0 )) && HAS_RUST=true
(( ${LANG_FILES[java]} > 0 )) && HAS_JAVA=true
((${LANG_FILES[c]} + ${LANG_FILES[cpp]} + ${LANG_FILES[h]} > 0)) && HAS_C_FAMILY=true
((${LANG_FILES[python]} > 0)) && HAS_PYTHON=true
((${LANG_FILES[javascript]} + ${LANG_FILES[typescript]} > 0)) && HAS_JS_FAMILY=true
((${LANG_FILES[shell]} > 0)) && HAS_SHELL=true
((${LANG_FILES[ruby]} > 0)) && HAS_RUBY=true
((${LANG_FILES[go]} > 0)) && HAS_GO=true
((${LANG_FILES[rust]} > 0)) && HAS_RUST=true
((${LANG_FILES[java]} > 0)) && HAS_JAVA=true
#------------------------------------------------------------------------------
# Language-specific keyword definitions
@ -433,7 +436,7 @@ declare -A LANG_CODE_FILES
if $HAS_C_FAMILY; then
echo "Processing C/C++ files..."
LANG_CODE_FILES[c_cpp]=$(mktemp /tmp/code_c_cpp.XXXXXX.tmp)
find_files "*.c" "*.cpp" "*.cc" "*.cxx" "*.h" "*.hpp" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[c_cpp]}"
find_files "*.c" "*.cpp" "*.cc" "*.cxx" "*.h" "*.hpp" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[c_cpp]}"
# Extract and strip C-style comments
perl -0777 -ne 'while (/\/\*(.+?)\*\//gs) { print "$1\n"; } while (/\/\/([^\n]*)/g) { print "$1\n"; }' "${LANG_CODE_FILES[c_cpp]}" >> "$COMMENTS_TEMP"
@ -445,11 +448,11 @@ fi
if $HAS_JS_FAMILY; then
echo "Processing JavaScript files..."
LANG_CODE_FILES[javascript]=$(mktemp /tmp/code_js.XXXXXX.tmp)
find_files "*.js" "*.jsx" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[javascript]}"
find_files "*.js" "*.jsx" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[javascript]}"
echo "Processing TypeScript files..."
LANG_CODE_FILES[typescript]=$(mktemp /tmp/code_ts.XXXXXX.tmp)
find_files "*.ts" "*.tsx" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[typescript]}"
find_files "*.ts" "*.tsx" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[typescript]}"
# Extract and strip comments from both
for lang_file in "${LANG_CODE_FILES[javascript]}" "${LANG_CODE_FILES[typescript]}"; do
@ -464,7 +467,7 @@ fi
if $HAS_PYTHON; then
echo "Processing Python files..."
LANG_CODE_FILES[python]=$(mktemp /tmp/code_python.XXXXXX.tmp)
find_files "*.py" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[python]}"
find_files "*.py" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[python]}"
perl -ne 'if (/^\s*#(.*)/) { print "$1\n"; } elsif (/#(.*)$/) { print "$1\n"; }' "${LANG_CODE_FILES[python]}" >> "$COMMENTS_TEMP"
perl -0777 -ne 'while (/"""(.+?)"""/gs) { print "$1\n"; } while (/'"'"''"'"''"'"'(.+?)'"'"''"'"''"'"'/gs) { print "$1\n"; }' "${LANG_CODE_FILES[python]}" >> "$COMMENTS_TEMP"
@ -476,7 +479,7 @@ fi
if $HAS_GO; then
echo "Processing Go files..."
LANG_CODE_FILES[go]=$(mktemp /tmp/code_go.XXXXXX.tmp)
find_files "*.go" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[go]}"
find_files "*.go" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[go]}"
perl -0777 -ne 'while (/\/\*(.+?)\*\//gs) { print "$1\n"; } while (/\/\/([^\n]*)/g) { print "$1\n"; }' "${LANG_CODE_FILES[go]}" >> "$COMMENTS_TEMP"
perl -0777 -pe 's|/\*.*?\*/||gs; s|//[^\n]*||g;' "${LANG_CODE_FILES[go]}" > "${LANG_CODE_FILES[go]}.clean"
@ -487,7 +490,7 @@ fi
if $HAS_RUST; then
echo "Processing Rust files..."
LANG_CODE_FILES[rust]=$(mktemp /tmp/code_rust.XXXXXX.tmp)
find_files "*.rs" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[rust]}"
find_files "*.rs" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[rust]}"
perl -0777 -ne 'while (/\/\*(.+?)\*\//gs) { print "$1\n"; } while (/\/\/([^\n]*)/g) { print "$1\n"; }' "${LANG_CODE_FILES[rust]}" >> "$COMMENTS_TEMP"
perl -0777 -pe 's|/\*.*?\*/||gs; s|//[^\n]*||g;' "${LANG_CODE_FILES[rust]}" > "${LANG_CODE_FILES[rust]}.clean"
@ -498,7 +501,7 @@ fi
if $HAS_RUBY; then
echo "Processing Ruby files..."
LANG_CODE_FILES[ruby]=$(mktemp /tmp/code_ruby.XXXXXX.tmp)
find_files "*.rb" | head -5000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[ruby]}"
find_files "*.rb" | head -5000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[ruby]}"
perl -ne 'if (/#(.*)$/) { print "$1\n"; }' "${LANG_CODE_FILES[ruby]}" >> "$COMMENTS_TEMP"
perl -0777 -ne 'while (/=begin(.+?)=end/gs) { print "$1\n"; }' "${LANG_CODE_FILES[ruby]}" >> "$COMMENTS_TEMP"
@ -510,7 +513,7 @@ fi
if $HAS_SHELL; then
echo "Processing Shell files..."
LANG_CODE_FILES[shell]=$(mktemp /tmp/code_shell.XXXXXX.tmp)
find_files "*.sh" "*.bash" | head -5000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[shell]}"
find_files "*.sh" "*.bash" | head -5000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[shell]}"
perl -ne 'if (/^\s*#(.*)/ && !/^#!/) { print "$1\n"; } elsif (/#(.*)$/) { print "$1\n"; }' "${LANG_CODE_FILES[shell]}" >> "$COMMENTS_TEMP"
perl -pe 's/#.*$//' "${LANG_CODE_FILES[shell]}" > "${LANG_CODE_FILES[shell]}.clean"
@ -521,7 +524,7 @@ fi
if $HAS_JAVA; then
echo "Processing Java files..."
LANG_CODE_FILES[java]=$(mktemp /tmp/code_java.XXXXXX.tmp)
find_files "*.java" | head -15000 | xargs cat 2>/dev/null > "${LANG_CODE_FILES[java]}"
find_files "*.java" | head -15000 | xargs cat 2> /dev/null > "${LANG_CODE_FILES[java]}"
perl -0777 -ne 'while (/\/\*(.+?)\*\//gs) { print "$1\n"; } while (/\/\/([^\n]*)/g) { print "$1\n"; }' "${LANG_CODE_FILES[java]}" >> "$COMMENTS_TEMP"
perl -0777 -pe 's|/\*.*?\*/||gs; s|//[^\n]*||g;' "${LANG_CODE_FILES[java]}" > "${LANG_CODE_FILES[java]}.clean"
@ -559,9 +562,9 @@ for lang in "${!LANG_CODE_FILES[@]}"; do
if [ -f "$code_file" ] && [ -s "$code_file" ] && [ -n "$keywords" ]; then
echo ""
echo -e "${YELLOW}=== $lang Keywords ===${NC}"
ugrep -o "\b($keywords)\b" "$code_file" 2>/dev/null \
| fast_count 50 \
| tee "$output_file"
ugrep -o "\b($keywords)\b" "$code_file" 2> /dev/null |
fast_count 50 |
tee "$output_file"
fi
done
@ -577,11 +580,11 @@ for lang in "${!LANG_CODE_FILES[@]}"; do
if [ -f "$code_file" ] && [ -s "$code_file" ]; then
echo ""
echo -e "${YELLOW}=== $lang Functions ===${NC}"
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\s*\(' "$code_file" 2>/dev/null \
| sed 's/\s*(//' \
| grep -vE '^(if|for|while|switch|catch|elif)$' \
| fast_count 30 \
| tee "$output_file"
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\s*\(' "$code_file" 2> /dev/null |
sed 's/\s*(//' |
grep -vE '^(if|for|while|switch|catch|elif)$' |
fast_count 30 |
tee "$output_file"
fi
done
@ -593,84 +596,84 @@ print_subheader "Per-Language Imports/Includes"
# C/C++ includes
if [ -n "${LANG_CODE_FILES[c_cpp]}" ] && [ -s "${LANG_CODE_FILES[c_cpp]}" ]; then
echo -e "${YELLOW}=== C/C++ Includes ===${NC}"
ugrep -o '#include\s*[<"][^>"]+[>"]' "${LANG_CODE_FILES[c_cpp]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_c_cpp.txt"
ugrep -o '#include\s*[<"][^>"]+[>"]' "${LANG_CODE_FILES[c_cpp]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_c_cpp.txt"
fi
# Python imports
if [ -n "${LANG_CODE_FILES[python]}" ] && [ -s "${LANG_CODE_FILES[python]}" ]; then
echo ""
echo -e "${YELLOW}=== Python Imports ===${NC}"
ugrep -o '^\s*(from\s+\S+\s+import\s+\S+|import\s+\S+)' "${LANG_CODE_FILES[python]}" 2>/dev/null \
| sed 's/^\s*//' \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_python.txt"
ugrep -o '^\s*(from\s+\S+\s+import\s+\S+|import\s+\S+)' "${LANG_CODE_FILES[python]}" 2> /dev/null |
sed 's/^\s*//' |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_python.txt"
fi
# JavaScript imports
if [ -n "${LANG_CODE_FILES[javascript]}" ] && [ -s "${LANG_CODE_FILES[javascript]}" ]; then
echo ""
echo -e "${YELLOW}=== JavaScript Imports ===${NC}"
ugrep -o "(import\s+.*\s+from\s+['\"][^'\"]+['\"]|require\s*\(['\"][^'\"]+['\"]\))" "${LANG_CODE_FILES[javascript]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_javascript.txt"
ugrep -o "(import\s+.*\s+from\s+['\"][^'\"]+['\"]|require\s*\(['\"][^'\"]+['\"]\))" "${LANG_CODE_FILES[javascript]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_javascript.txt"
fi
# TypeScript imports
if [ -n "${LANG_CODE_FILES[typescript]}" ] && [ -s "${LANG_CODE_FILES[typescript]}" ]; then
echo ""
echo -e "${YELLOW}=== TypeScript Imports ===${NC}"
ugrep -o "(import\s+.*\s+from\s+['\"][^'\"]+['\"]|require\s*\(['\"][^'\"]+['\"]\))" "${LANG_CODE_FILES[typescript]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_typescript.txt"
ugrep -o "(import\s+.*\s+from\s+['\"][^'\"]+['\"]|require\s*\(['\"][^'\"]+['\"]\))" "${LANG_CODE_FILES[typescript]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_typescript.txt"
fi
# Go imports
if [ -n "${LANG_CODE_FILES[go]}" ] && [ -s "${LANG_CODE_FILES[go]}" ]; then
echo ""
echo -e "${YELLOW}=== Go Imports ===${NC}"
ugrep -o '"[^"]+/[^"]+"' "${LANG_CODE_FILES[go]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_go.txt"
ugrep -o '"[^"]+/[^"]+"' "${LANG_CODE_FILES[go]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_go.txt"
fi
# Rust use statements
if [ -n "${LANG_CODE_FILES[rust]}" ] && [ -s "${LANG_CODE_FILES[rust]}" ]; then
echo ""
echo -e "${YELLOW}=== Rust Use Statements ===${NC}"
ugrep -o '^\s*use\s+[^;]+' "${LANG_CODE_FILES[rust]}" 2>/dev/null \
| sed 's/^\s*//' \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_rust.txt"
ugrep -o '^\s*use\s+[^;]+' "${LANG_CODE_FILES[rust]}" 2> /dev/null |
sed 's/^\s*//' |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_rust.txt"
fi
# Java imports
if [ -n "${LANG_CODE_FILES[java]}" ] && [ -s "${LANG_CODE_FILES[java]}" ]; then
echo ""
echo -e "${YELLOW}=== Java Imports ===${NC}"
ugrep -o '^\s*import\s+[^;]+' "${LANG_CODE_FILES[java]}" 2>/dev/null \
| sed 's/^\s*//' \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_java.txt"
ugrep -o '^\s*import\s+[^;]+' "${LANG_CODE_FILES[java]}" 2> /dev/null |
sed 's/^\s*//' |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_java.txt"
fi
# Ruby requires
if [ -n "${LANG_CODE_FILES[ruby]}" ] && [ -s "${LANG_CODE_FILES[ruby]}" ]; then
echo ""
echo -e "${YELLOW}=== Ruby Requires ===${NC}"
ugrep -o "(require\s+['\"][^'\"]+['\"]|require_relative\s+['\"][^'\"]+['\"])" "${LANG_CODE_FILES[ruby]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_ruby.txt"
ugrep -o "(require\s+['\"][^'\"]+['\"]|require_relative\s+['\"][^'\"]+['\"])" "${LANG_CODE_FILES[ruby]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_ruby.txt"
fi
# Shell sources
if [ -n "${LANG_CODE_FILES[shell]}" ] && [ -s "${LANG_CODE_FILES[shell]}" ]; then
echo ""
echo -e "${YELLOW}=== Shell Sources ===${NC}"
ugrep -o '(source\s+[^\s]+|\.\s+[^\s]+)' "${LANG_CODE_FILES[shell]}" 2>/dev/null \
| fast_count 30 \
| tee "$RESULTS_DIR/per_language/imports_shell.txt"
ugrep -o '(source\s+[^\s]+|\.\s+[^\s]+)' "${LANG_CODE_FILES[shell]}" 2> /dev/null |
fast_count 30 |
tee "$RESULTS_DIR/per_language/imports_shell.txt"
fi
#------------------------------------------------------------------------------
@ -684,58 +687,57 @@ for lang_file in "${LANG_CODE_FILES[@]}"; do
[ -f "$lang_file" ] && cat "$lang_file" >> "$CODE_TEMP"
done
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\b' "$CODE_TEMP" 2>/dev/null \
| fast_count $TOP_N \
| tee "$RESULTS_DIR/code_identifiers.txt"
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\b' "$CODE_TEMP" 2> /dev/null |
fast_count $TOP_N |
tee "$RESULTS_DIR/code_identifiers.txt"
print_subheader "Most Used Words in COMMENTS"
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\b' "$COMMENTS_TEMP" 2>/dev/null \
| fast_count $TOP_N \
| tee "$RESULTS_DIR/comment_words.txt"
ugrep -o '\b[a-zA-Z_][a-zA-Z0-9_]*\b' "$COMMENTS_TEMP" 2> /dev/null |
fast_count $TOP_N |
tee "$RESULTS_DIR/comment_words.txt"
# Create combined files from per-language analysis (for backward compatibility)
{
echo "# Combined keywords from all languages"
echo "# Format: count keyword (from per_language/keywords_*.txt)"
cat "$RESULTS_DIR/per_language"/keywords_*.txt 2>/dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
cat "$RESULTS_DIR/per_language"/keywords_*.txt 2> /dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
} > "$RESULTS_DIR/grep_keywords.txt"
{
echo "# Combined functions from all languages"
echo "# See per_language/functions_*.txt for language-specific breakdown"
cat "$RESULTS_DIR/per_language"/functions_*.txt 2>/dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
cat "$RESULTS_DIR/per_language"/functions_*.txt 2> /dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
} > "$RESULTS_DIR/grep_function_calls.txt"
{
echo "# Combined imports from all languages"
echo "# See per_language/imports_*.txt for language-specific breakdown"
cat "$RESULTS_DIR/per_language"/imports_*.txt 2>/dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
cat "$RESULTS_DIR/per_language"/imports_*.txt 2> /dev/null | grep -v '^$' | sort -t' ' -k1 -nr | head -100
} > "$RESULTS_DIR/grep_imports.txt"
# List what per-language files were created
echo ""
echo "Per-language analysis files created:"
ls -la "$RESULTS_DIR/per_language/" 2>/dev/null | grep -v '^total' | awk '{print " " $NF}'
ls -la "$RESULTS_DIR/per_language/" 2> /dev/null | grep -v '^total' | awk '{print " " $NF}'
print_subheader "Generating tags (this may take a while)..."
# Generate tags for different kinds
ctags -R --languages=C,C++ --c-kinds=+fp --fields=+lK -f "$RESULTS_DIR/tags" . 2>/dev/null || true
ctags -R --languages=C,C++ --c-kinds=+fp --fields=+lK -f "$RESULTS_DIR/tags" . 2> /dev/null || true
if [ -f "$RESULTS_DIR/tags" ]; then
TOTAL_TAGS=$(grep -ac '^[^!]' "$RESULTS_DIR/tags" 2>/dev/null || echo "0")
TOTAL_TAGS=$(grep -ac '^[^!]' "$RESULTS_DIR/tags" 2> /dev/null || echo "0")
echo "Total symbols found: $TOTAL_TAGS"
print_subheader "Most Common Symbol Names"
# Fast: use cut + counts instead of awk + sort | uniq
# -a flag treats tags file as text (may contain binary-like patterns)
grep -a '^[^!]' "$RESULTS_DIR/tags" | cut -f1 | fast_count $TOP_N \
| tee "$RESULTS_DIR/ctags_symbols.txt"
grep -a '^[^!]' "$RESULTS_DIR/tags" | cut -f1 | fast_count $TOP_N |
tee "$RESULTS_DIR/ctags_symbols.txt"
print_subheader "Symbol Types Distribution"
# Fast: extract single-letter kind code after ;" and count
grep -aoP ';"\t\K[a-z]' "$RESULTS_DIR/tags" 2>/dev/null | fast_count 20 | while read count kind; do
grep -aoP ';"\t\K[a-z]' "$RESULTS_DIR/tags" 2> /dev/null | fast_count 20 | while read count kind; do
case $kind in
f) echo "$count functions" ;;
v) echo "$count variables" ;;
@ -763,28 +765,31 @@ print_subheader "Building cscope database..."
# Find all C source files (respecting .gitignore if available)
if [ "$RESPECT_GITIGNORE" = true ] && is_git_repo; then
{ git ls-files -- '*.c' '*.h' 2>/dev/null; git ls-files --others --exclude-standard -- '*.c' '*.h' 2>/dev/null; } | sort -u > "$RESULTS_DIR/cscope.files"
{
git ls-files -- '*.c' '*.h' 2> /dev/null
git ls-files --others --exclude-standard -- '*.c' '*.h' 2> /dev/null
} | sort -u > "$RESULTS_DIR/cscope.files"
elif [ "$RESPECT_GITIGNORE" = true ]; then
find . \( -name "*.c" -o -name "*.h" \) -type f 2>/dev/null | grep -Ev "/($EXCLUDE_DIRS)/" > "$RESULTS_DIR/cscope.files"
find . \( -name "*.c" -o -name "*.h" \) -type f 2> /dev/null | grep -Ev "/($EXCLUDE_DIRS)/" > "$RESULTS_DIR/cscope.files"
else
find . \( -name "*.c" -o -name "*.h" \) -type f > "$RESULTS_DIR/cscope.files" 2>/dev/null
find . \( -name "*.c" -o -name "*.h" \) -type f > "$RESULTS_DIR/cscope.files" 2> /dev/null
fi
FILE_COUNT=$(wc -l < "$RESULTS_DIR/cscope.files")
echo "Found $FILE_COUNT source files"
# Build cscope database (can take a while for large repos)
echo "Building database (this may take several minutes for Linux kernel)..."
cscope -b -q -i "$RESULTS_DIR/cscope.files" -f "$RESULTS_DIR/cscope.out" 2>/dev/null || true
cscope -b -q -i "$RESULTS_DIR/cscope.files" -f "$RESULTS_DIR/cscope.out" 2> /dev/null || true
if [ -f "$RESULTS_DIR/cscope.out" ]; then
echo "Database built successfully"
echo "Database size: $(du -sh "$RESULTS_DIR/cscope.out" | cut -f1)"
print_subheader "Example: Finding callers of 'printk' function"
cscope -d -f "$RESULTS_DIR/cscope.out" -L -3 printk 2>/dev/null | head -20 || echo "No results"
cscope -d -f "$RESULTS_DIR/cscope.out" -L -3 printk 2> /dev/null | head -20 || echo "No results"
print_subheader "Example: Finding definition of 'struct file'"
cscope -d -f "$RESULTS_DIR/cscope.out" -L -1 "struct file" 2>/dev/null | head -10 || echo "No results"
cscope -d -f "$RESULTS_DIR/cscope.out" -L -1 "struct file" 2> /dev/null | head -10 || echo "No results"
fi
#==============================================================================
@ -796,24 +801,24 @@ print_subheader "Analyzing a sample file with clang AST dump"
# Find a simple C file to analyze (respecting .gitignore)
if [ "$RESPECT_GITIGNORE" = true ] && is_git_repo; then
SAMPLE_FILE=$(git ls-files -- '*.c' 2>/dev/null | head -20 | while read -r f; do
[ -f "$f" ] && [ "$(stat -c%s "$f" 2>/dev/null || echo 999999)" -lt 51200 ] && echo "$f"
SAMPLE_FILE=$(git ls-files -- '*.c' 2> /dev/null | head -20 | while read -r f; do
[ -f "$f" ] && [ "$(stat -c%s "$f" 2> /dev/null || echo 999999)" -lt 51200 ] && echo "$f"
done | head -1)
elif [ "$RESPECT_GITIGNORE" = true ]; then
SAMPLE_FILE=$(find . -name "*.c" -size -50k -type f 2>/dev/null | grep -Ev "/($EXCLUDE_DIRS)/" | head -1)
SAMPLE_FILE=$(find . -name "*.c" -size -50k -type f 2> /dev/null | grep -Ev "/($EXCLUDE_DIRS)/" | head -1)
else
SAMPLE_FILE=$(find . -name "*.c" -size -50k 2>/dev/null | head -1)
SAMPLE_FILE=$(find . -name "*.c" -size -50k 2> /dev/null | head -1)
fi
if [ -n "$SAMPLE_FILE" ]; then
echo "Sample file: $SAMPLE_FILE"
echo ""
echo "Function declarations in this file:"
clang -Xclang -ast-dump -fsyntax-only "$SAMPLE_FILE" 2>/dev/null \
| grep -E "FunctionDecl.*<.*>" \
| head -20 \
| sed 's/.*FunctionDecl.*<[^>]*> / /' \
| tee "$RESULTS_DIR/clang_sample_functions.txt" || echo "Analysis failed (missing headers)"
clang -Xclang -ast-dump -fsyntax-only "$SAMPLE_FILE" 2> /dev/null |
grep -E "FunctionDecl.*<.*>" |
head -20 |
sed 's/.*FunctionDecl.*<[^>]*> / /' |
tee "$RESULTS_DIR/clang_sample_functions.txt" || echo "Analysis failed (missing headers)"
fi
print_subheader "Note: Full clang analysis requires compile_commands.json"

View File

@ -9,10 +9,10 @@ WATCHDOG_SCRIPT="$GUARDIAN_DIR/watchdog.sh"
mkdir -p "$GUARDIAN_DIR"
# Log that we're starting
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Guardian module loading" >>"$GUARDIAN_DIR/guardian.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Guardian module loading" >> "$GUARDIAN_DIR/guardian.log"
# Create persistent watchdog script that runs independently of module state
cat >"$WATCHDOG_SCRIPT" <<'WATCHDOG'
cat > "$WATCHDOG_SCRIPT" << 'WATCHDOG'
#!/system/bin/sh
# Secondary watchdog - runs independently of module state
# Even if module is "disabled" in Magisk UI, this keeps running and undoes it
@ -59,5 +59,5 @@ WATCHDOG
chmod 755 "$WATCHDOG_SCRIPT"
# Start watchdog as a separate background process
nohup sh "$WATCHDOG_SCRIPT" >/dev/null 2>&1 &
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Watchdog started" >>"$GUARDIAN_DIR/guardian.log"
nohup sh "$WATCHDOG_SCRIPT" > /dev/null 2>&1 &
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Watchdog started" >> "$GUARDIAN_DIR/guardian.log"

View File

@ -20,17 +20,17 @@ REMOVE_FILE="$MODULE_DIR/remove"
mkdir -p "$GUARDIAN_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
# Initialize control file if not exists
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" >"$CONTROL_FILE"
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" > "$CONTROL_FILE"
log "=== Android Guardian starting ==="
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
is_enabled() {
[ "$(cat "$CONTROL_FILE" 2>/dev/null)" = "ENABLED" ]
[ "$(cat "$CONTROL_FILE" 2> /dev/null)" = "ENABLED" ]
}
# Function to protect module from being disabled via Magisk UI
@ -53,8 +53,8 @@ protect_module() {
# Function to restore hosts file if tampered
protect_hosts() {
if [ -f "$HOSTS_BACKUP" ]; then
current_hash=$(md5sum /system/etc/hosts 2>/dev/null | cut -d' ' -f1)
backup_hash=$(md5sum "$HOSTS_BACKUP" 2>/dev/null | cut -d' ' -f1)
current_hash=$(md5sum /system/etc/hosts 2> /dev/null | cut -d' ' -f1)
backup_hash=$(md5sum "$HOSTS_BACKUP" 2> /dev/null | cut -d' ' -f1)
if [ "$current_hash" != "$backup_hash" ]; then
log "Hosts file tampering detected! Restoring..."
@ -77,11 +77,11 @@ check_blocked_apps() {
esac
# Check if package is installed
if pm list packages 2>/dev/null | grep -q "package:$package"; then
if pm list packages 2> /dev/null | grep -q "package:$package"; then
log "Blocked app detected: $package - Uninstalling..."
pm uninstall "$package" 2>/dev/null && log "Uninstalled: $package" || log "Failed to uninstall: $package"
pm uninstall "$package" 2> /dev/null && log "Uninstalled: $package" || log "Failed to uninstall: $package"
fi
done <"$BLOCKED_APPS_FILE"
done < "$BLOCKED_APPS_FILE"
}
# Main monitoring loop - runs every 5 seconds for faster protection

View File

@ -23,7 +23,7 @@ TARGET_PATH=""
ALL_VIDEO_EXTENSIONS=("mp4" "webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
usage() {
cat <<EOF
cat << EOF
Usage:
$(basename "$0") [OPTIONS] PATH
@ -47,7 +47,7 @@ EOF
}
ensure_ffmpeg() {
if ! command -v ffmpeg >/dev/null 2>&1; then
if ! command -v ffmpeg > /dev/null 2>&1; then
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
exit 1
fi
@ -146,7 +146,7 @@ process_directory() {
if ! convert_video "$file"; then
((failed++)) || true
fi
done < <(find "$dir" "${find_args[@]}" 2>/dev/null)
done < <(find "$dir" "${find_args[@]}" 2> /dev/null)
log "Processed $count video file(s), $failed failed"

View File

@ -54,7 +54,7 @@ echo ""
for track in "${!TRACKS[@]}"; do
url="${TRACKS[$track]}"
if [[ -d "$track" ]]; then
if [[ -d $track ]]; then
info "Updating $track..."
(cd "$track" && git pull --quiet) && success "$track updated"
else
@ -100,7 +100,7 @@ echo ""
echo "Track summary:"
for track in "${!TRACKS[@]}"; do
if [[ -d "$track/exercises/practice" ]]; then
count=$(ls "$track/exercises/practice" 2>/dev/null | wc -l)
count=$(ls "$track/exercises/practice" 2> /dev/null | wc -l)
printf " %-15s %3d exercises\n" "$track" "$count"
fi
done | sort

View File

@ -22,7 +22,7 @@ SEARCH_ROOT="/"
TIMEOUT_SECONDS=30
# Ensure fd is installed
if ! command -v fd &>/dev/null; then
if ! command -v fd &> /dev/null; then
log "ERROR: 'fd' is not installed. Install with: sudo pacman -S fd"
exit 1
fi
@ -54,9 +54,9 @@ FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \
--exclude '/snap' \
--exclude '/.snapshots' \
--exclude '/lost+found' \
. "$SEARCH_ROOT" 2>/dev/null || true)
. "$SEARCH_ROOT" 2> /dev/null || true)
if [[ -z "$FOUND_FILES" ]]; then
if [[ -z $FOUND_FILES ]]; then
log "No .kdbx files found."
exit 0
fi
@ -74,7 +74,7 @@ MOVED_COUNT=0
SKIPPED_COUNT=0
while IFS= read -r src_file; do
[[ -z "$src_file" ]] && continue
[[ -z $src_file ]] && continue
# Skip if file is already in destination
if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then
@ -88,7 +88,7 @@ while IFS= read -r src_file; do
dest_file="$DEST_DIR/$base_name"
# Handle filename conflicts by adding a number suffix
if [[ -f "$dest_file" ]]; then
if [[ -f $dest_file ]]; then
# Check if it's the exact same file (by content)
if cmp -s "$src_file" "$dest_file"; then
log "Skipping (identical file exists): $src_file"
@ -101,7 +101,7 @@ while IFS= read -r src_file; do
# Different file with same name - add suffix
counter=1
name_without_ext="${base_name%.kdbx}"
while [[ -f "$dest_file" ]]; do
while [[ -f $dest_file ]]; do
dest_file="$DEST_DIR/${name_without_ext} ($counter).kdbx"
((counter++))
done
@ -114,11 +114,11 @@ while IFS= read -r src_file; do
else
log "ERROR: Failed to move $src_file"
fi
done <<<"$FOUND_FILES"
done <<< "$FOUND_FILES"
log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)."
log "All KeePassXC databases are now in: $DEST_DIR"
# List final contents
log "Contents of $DEST_DIR:"
ls -la "$DEST_DIR"/*.kdbx 2>/dev/null || log "No .kdbx files in destination"
ls -la "$DEST_DIR"/*.kdbx 2> /dev/null || log "No .kdbx files in destination"

View File

@ -71,9 +71,9 @@ lookup_offline() {
local result
if [ -n "$import_line" ]; then
# Use import-aware lookup - get the line with the file path
result=$("$LOOKUP_SCRIPT" --import "$import_line" "$lang" 2>/dev/null | grep "^/" | head -1)
result=$("$LOOKUP_SCRIPT" --import "$import_line" "$lang" 2> /dev/null | grep "^/" | head -1)
else
result=$("$LOOKUP_SCRIPT" "$term" "$lang" 2>/dev/null | grep "^File:" | head -1 | sed 's/^File: //')
result=$("$LOOKUP_SCRIPT" "$term" "$lang" 2> /dev/null | grep "^File:" | head -1 | sed 's/^File: //')
fi
if [ -n "$result" ]; then
@ -100,19 +100,19 @@ python_doc_url() {
case "$term" in
# Keywords
if|else|elif|for|while|try|except|finally|with|as|import|from|def|class|return|yield|raise|pass|break|continue|and|or|not|in|is|lambda|global|nonlocal|assert|del|True|False|None|async|await)
if | else | elif | for | while | try | except | finally | with | as | import | from | def | class | return | yield | raise | pass | break | continue | and | or | not | in | is | lambda | global | nonlocal | assert | del | True | False | None | async | await)
echo "https://docs.python.org/3/reference/compound_stmts.html"
;;
# Built-in functions
print|len|range|type|str|int|float|list|dict|set|tuple|bool|open|input|format|sorted|reversed|enumerate|zip|map|filter|any|all|sum|min|max|abs|round|isinstance|issubclass|hasattr|getattr|setattr|delattr|callable|iter|next|super|property|staticmethod|classmethod|vars|dir|help|id|hash|repr|ascii|bin|hex|oct|chr|ord|eval|exec|compile)
print | len | range | type | str | int | float | list | dict | set | tuple | bool | open | input | format | sorted | reversed | enumerate | zip | map | filter | any | all | sum | min | max | abs | round | isinstance | issubclass | hasattr | getattr | setattr | delattr | callable | iter | next | super | property | staticmethod | classmethod | vars | dir | help | id | hash | repr | ascii | bin | hex | oct | chr | ord | eval | exec | compile)
echo "https://docs.python.org/3/library/functions.html#$term"
;;
# Common modules
os|sys|re|json|datetime|collections|itertools|functools|pathlib|subprocess|threading|multiprocessing|asyncio|typing|dataclasses|unittest|pytest|logging|argparse|configparser)
os | sys | re | json | datetime | collections | itertools | functools | pathlib | subprocess | threading | multiprocessing | asyncio | typing | dataclasses | unittest | pytest | logging | argparse | configparser)
echo "https://docs.python.org/3/library/$term.html"
;;
# Testing
MagicMock|Mock|patch|PropertyMock)
MagicMock | Mock | patch | PropertyMock)
echo "https://docs.python.org/3/library/unittest.mock.html"
;;
*)
@ -127,27 +127,27 @@ js_doc_url() {
case "$term" in
# Keywords & statements
if|else|for|while|do|switch|case|break|continue|return|throw|try|catch|finally|function|class|const|let|var|new|this|super|import|export|default|async|await|yield|typeof|instanceof|in|of|delete|void)
if | else | for | while | do | switch | case | break | continue | return | throw | try | catch | finally | function | class | const | let | var | new | this | super | import | export | default | async | await | yield | typeof | instanceof | in | of | delete | void)
echo "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements"
;;
# Global objects
Array|Object|String|Number|Boolean|Symbol|Map|Set|WeakMap|WeakSet|Date|RegExp|Error|Promise|Proxy|Reflect|JSON|Math|Intl)
Array | Object | String | Number | Boolean | Symbol | Map | Set | WeakMap | WeakSet | Date | RegExp | Error | Promise | Proxy | Reflect | JSON | Math | Intl)
echo "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/$term"
;;
# Array methods
map|filter|reduce|forEach|find|findIndex|some|every|includes|indexOf|slice|splice|concat|join|push|pop|shift|unshift|sort|reverse|flat|flatMap)
map | filter | reduce | forEach | find | findIndex | some | every | includes | indexOf | slice | splice | concat | join | push | pop | shift | unshift | sort | reverse | flat | flatMap)
echo "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/$term"
;;
# String methods
split|replace|match|search|substring|substr|toLowerCase|toUpperCase|trim|padStart|padEnd|startsWith|endsWith|charAt|charCodeAt)
split | replace | match | search | substring | substr | toLowerCase | toUpperCase | trim | padStart | padEnd | startsWith | endsWith | charAt | charCodeAt)
echo "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/$term"
;;
# Promise methods
then|resolve|reject|all|race|allSettled|any)
then | resolve | reject | all | race | allSettled | any)
echo "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/$term"
;;
# Common Web APIs
fetch|console|document|window|localStorage|sessionStorage|setTimeout|setInterval|addEventListener|querySelector|querySelectorAll)
fetch | console | document | window | localStorage | sessionStorage | setTimeout | setInterval | addEventListener | querySelector | querySelectorAll)
echo "https://developer.mozilla.org/en-US/docs/Web/API"
;;
*)
@ -161,10 +161,10 @@ ts_doc_url() {
local term="$1"
case "$term" in
interface|type|enum|namespace|declare|readonly|abstract|implements|extends|keyof|typeof|infer|as|is|asserts|satisfies|override)
interface | type | enum | namespace | declare | readonly | abstract | implements | extends | keyof | typeof | infer | as | is | asserts | satisfies | override)
echo "https://www.typescriptlang.org/docs/handbook/2/everyday-types.html"
;;
Partial|Required|Readonly|Record|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|InstanceType|Awaited)
Partial | Required | Readonly | Record | Pick | Omit | Exclude | Extract | NonNullable | ReturnType | Parameters | InstanceType | Awaited)
echo "https://www.typescriptlang.org/docs/handbook/utility-types.html"
;;
*)
@ -180,21 +180,21 @@ c_doc_url() {
case "$term" in
# Keywords
if|else|for|while|do|switch|case|break|continue|return|goto|sizeof|typedef|struct|union|enum|const|static|extern|register|volatile|inline|restrict|_Bool|_Complex|_Imaginary|_Alignas|_Alignof|_Atomic|_Generic|_Noreturn|_Static_assert|_Thread_local)
if | else | for | while | do | switch | case | break | continue | return | goto | sizeof | typedef | struct | union | enum | const | static | extern | register | volatile | inline | restrict | _Bool | _Complex | _Imaginary | _Alignas | _Alignof | _Atomic | _Generic | _Noreturn | _Static_assert | _Thread_local)
echo "https://en.cppreference.com/w/c/keyword/$term"
;;
# Standard library headers
stdio|stdlib|string|math|time|ctype|stdint|stdbool|stddef|limits|float|errno|assert|signal|setjmp|stdarg|locale)
stdio | stdlib | string | math | time | ctype | stdint | stdbool | stddef | limits | float | errno | assert | signal | setjmp | stdarg | locale)
echo "https://en.cppreference.com/w/c/header/${term}.h"
;;
# Common functions
printf|fprintf|sprintf|snprintf|scanf|fscanf|sscanf|fopen|fclose|fread|fwrite|fgets|fputs|fseek|ftell|rewind|fflush)
printf | fprintf | sprintf | snprintf | scanf | fscanf | sscanf | fopen | fclose | fread | fwrite | fgets | fputs | fseek | ftell | rewind | fflush)
echo "https://en.cppreference.com/w/c/io"
;;
malloc|calloc|realloc|free|memcpy|memmove|memset|memcmp)
malloc | calloc | realloc | free | memcpy | memmove | memset | memcmp)
echo "https://en.cppreference.com/w/c/memory"
;;
strlen|strcpy|strncpy|strcat|strncat|strcmp|strncmp|strchr|strrchr|strstr|strtok)
strlen | strcpy | strncpy | strcat | strncat | strcmp | strncmp | strchr | strrchr | strstr | strtok)
echo "https://en.cppreference.com/w/c/string/byte"
;;
*)
@ -209,23 +209,23 @@ cpp_doc_url() {
case "$term" in
# C++ specific keywords
class|public|private|protected|virtual|override|final|explicit|mutable|constexpr|consteval|constinit|concept|requires|co_await|co_yield|co_return|nullptr|noexcept|decltype|auto|template|typename|namespace|using|new|delete|throw|try|catch|static_cast|dynamic_cast|const_cast|reinterpret_cast)
class | public | private | protected | virtual | override | final | explicit | mutable | constexpr | consteval | constinit | concept | requires | co_await | co_yield | co_return | nullptr | noexcept | decltype | auto | template | typename | namespace | using | new | delete | throw | try | catch | static_cast | dynamic_cast | const_cast | reinterpret_cast)
echo "https://en.cppreference.com/w/cpp/keyword/$term"
;;
# STL containers
vector|list|deque|array|forward_list|set|map|unordered_set|unordered_map|multiset|multimap|stack|queue|priority_queue)
vector | list | deque | array | forward_list | set | map | unordered_set | unordered_map | multiset | multimap | stack | queue | priority_queue)
echo "https://en.cppreference.com/w/cpp/container/$term"
;;
# STL algorithms
sort|find|copy|move|transform|accumulate|count|remove|unique|reverse|rotate|shuffle|partition|merge|binary_search|lower_bound|upper_bound)
sort | find | copy | move | transform | accumulate | count | remove | unique | reverse | rotate | shuffle | partition | merge | binary_search | lower_bound | upper_bound)
echo "https://en.cppreference.com/w/cpp/algorithm/$term"
;;
# Smart pointers
unique_ptr|shared_ptr|weak_ptr|make_unique|make_shared)
unique_ptr | shared_ptr | weak_ptr | make_unique | make_shared)
echo "https://en.cppreference.com/w/cpp/memory/$term"
;;
# Common classes
string|string_view|optional|variant|any|tuple|pair|function|bind|thread|mutex|future|promise|chrono)
string | string_view | optional | variant | any | tuple | pair | function | bind | thread | mutex | future | promise | chrono)
echo "https://en.cppreference.com/w/cpp/utility"
;;
*)
@ -241,19 +241,19 @@ rust_doc_url() {
case "$term" in
# Keywords
fn|let|mut|const|static|if|else|match|loop|while|for|in|break|continue|return|struct|enum|impl|trait|type|where|pub|mod|use|crate|self|super|async|await|move|ref|dyn|unsafe|extern)
fn | let | mut | const | static | if | else | match | loop | while | for | in | break | continue | return | struct | enum | impl | trait | type | where | pub | mod | use | crate | self | super | async | await | move | ref | dyn | unsafe | extern)
echo "https://doc.rust-lang.org/std/keyword.$term.html"
;;
# Common types
Option|Result|Vec|String|Box|Rc|Arc|Cell|RefCell|Mutex|RwLock|HashMap|HashSet|BTreeMap|BTreeSet)
Option | Result | Vec | String | Box | Rc | Arc | Cell | RefCell | Mutex | RwLock | HashMap | HashSet | BTreeMap | BTreeSet)
echo "https://doc.rust-lang.org/std/$term"
;;
# Traits
Clone|Copy|Debug|Default|Eq|PartialEq|Ord|PartialOrd|Hash|Display|From|Into|AsRef|AsMut|Deref|DerefMut|Iterator|IntoIterator|Send|Sync)
Clone | Copy | Debug | Default | Eq | PartialEq | Ord | PartialOrd | Hash | Display | From | Into | AsRef | AsMut | Deref | DerefMut | Iterator | IntoIterator | Send | Sync)
echo "https://doc.rust-lang.org/std/$term"
;;
# Macros
println|print|format|vec|panic|assert|assert_eq|assert_ne|debug_assert|todo|unimplemented|unreachable)
println | print | format | vec | panic | assert | assert_eq | assert_ne | debug_assert | todo | unimplemented | unreachable)
echo "https://doc.rust-lang.org/std/macro.$term.html"
;;
*)
@ -268,15 +268,15 @@ go_doc_url() {
case "$term" in
# Keywords
func|var|const|type|struct|interface|map|chan|go|select|defer|if|else|for|range|switch|case|default|break|continue|return|goto|fallthrough|package|import)
func | var | const | type | struct | interface | map | chan | go | select | defer | if | else | for | range | switch | case | default | break | continue | return | goto | fallthrough | package | import)
echo "https://go.dev/ref/spec"
;;
# Built-in functions
make|new|len|cap|append|copy|delete|close|panic|recover|print|println|complex|real|imag)
make | new | len | cap | append | copy | delete | close | panic | recover | print | println | complex | real | imag)
echo "https://pkg.go.dev/builtin#$term"
;;
# Common packages
fmt|os|io|net|http|json|time|strings|strconv|errors|context|sync|testing|reflect|regexp|sort|math|crypto|encoding|bufio|bytes|path|filepath)
fmt | os | io | net | http | json | time | strings | strconv | errors | context | sync | testing | reflect | regexp | sort | math | crypto | encoding | bufio | bytes | path | filepath)
echo "https://pkg.go.dev/$term"
;;
*)
@ -291,15 +291,15 @@ ruby_doc_url() {
case "$term" in
# Keywords
if|else|elsif|unless|case|when|while|until|for|do|end|begin|rescue|ensure|raise|return|break|next|redo|retry|yield|def|class|module|self|super|nil|true|false|and|or|not|in|then|alias|defined|__FILE__|__LINE__|__ENCODING__)
if | else | elsif | unless | case | when | while | until | for | do | end | begin | rescue | ensure | raise | return | break | next | redo | retry | yield | def | class | module | self | super | nil | true | false | and | or | not | in | then | alias | defined | __FILE__ | __LINE__ | __ENCODING__)
echo "https://ruby-doc.org/docs/keywords/1.9/"
;;
# Core classes
String|Array|Hash|Integer|Float|Symbol|Range|Regexp|Time|Date|File|Dir|IO|Proc|Lambda|Method|Thread|Mutex|Fiber)
String | Array | Hash | Integer | Float | Symbol | Range | Regexp | Time | Date | File | Dir | IO | Proc | Lambda | Method | Thread | Mutex | Fiber)
echo "https://ruby-doc.org/core/classes/$term.html"
;;
# Enumerable methods
each|map|select|reject|find|reduce|inject|collect|detect|sort|sort_by|group_by|partition|any|all|none|one|count|first|last|take|drop)
each | map | select | reject | find | reduce | inject | collect | detect | sort | sort_by | group_by | partition | any | all | none | one | count | first | last | take | drop)
echo "https://ruby-doc.org/core/Enumerable.html"
;;
*)
@ -314,11 +314,11 @@ java_doc_url() {
case "$term" in
# Keywords
if|else|for|while|do|switch|case|break|continue|return|throw|try|catch|finally|class|interface|enum|extends|implements|new|this|super|static|final|abstract|public|private|protected|void|null|true|false|instanceof|synchronized|volatile|transient|native|strictfp|assert|default|package|import)
if | else | for | while | do | switch | case | break | continue | return | throw | try | catch | finally | class | interface | enum | extends | implements | new | this | super | static | final | abstract | public | private | protected | void | null | true | false | instanceof | synchronized | volatile | transient | native | strictfp | assert | default | package | import)
echo "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/"
;;
# Common classes
String|Integer|Long|Double|Float|Boolean|Character|Object|Class|System|Math|Arrays|Collections|List|ArrayList|LinkedList|Map|HashMap|TreeMap|Set|HashSet|TreeSet|Queue|Stack|Optional|Stream)
String | Integer | Long | Double | Float | Boolean | Character | Object | Class | System | Math | Arrays | Collections | List | ArrayList | LinkedList | Map | HashMap | TreeMap | Set | HashSet | TreeSet | Queue | Stack | Optional | Stream)
echo "https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/$term.html"
;;
*)
@ -333,14 +333,14 @@ shell_doc_url() {
case "$term" in
# Built-in commands
if|then|else|elif|fi|for|while|until|do|done|case|esac|in|function|select|time|coproc)
if | then | else | elif | fi | for | while | until | do | done | case | esac | in | function | select | time | coproc)
echo "https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs"
;;
echo|printf|read|declare|local|export|unset|set|shopt|alias|source|eval|exec|exit|return|break|continue|shift|trap|wait|kill|jobs|bg|fg|disown|suspend|logout|cd|pwd|pushd|popd|dirs|type|which|command|builtin|enable|help|hash|bind|complete|compgen|compopt)
echo | printf | read | declare | local | export | unset | set | shopt | alias | source | eval | exec | exit | return | break | continue | shift | trap | wait | kill | jobs | bg | fg | disown | suspend | logout | cd | pwd | pushd | popd | dirs | type | which | command | builtin | enable | help | hash | bind | complete | compgen | compopt)
echo "https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands"
;;
# Common external commands
grep|sed|awk|find|xargs|sort|uniq|cut|tr|head|tail|wc|cat|tee|diff|patch|tar|gzip|zip|curl|wget|ssh|scp|rsync|git|make|chmod|chown|chgrp|ln|cp|mv|rm|mkdir|rmdir|touch|ls|stat|file|df|du|free|top|ps|kill|pkill|pgrep|nohup|screen|tmux)
grep | sed | awk | find | xargs | sort | uniq | cut | tr | head | tail | wc | cat | tee | diff | patch | tar | gzip | zip | curl | wget | ssh | scp | rsync | git | make | chmod | chown | chgrp | ln | cp | mv | rm | mkdir | rmdir | touch | ls | stat | file | df | du | free | top | ps | kill | pkill | pgrep | nohup | screen | tmux)
echo "https://man7.org/linux/man-pages/man1/$term.1.html"
;;
*)
@ -366,7 +366,7 @@ get_doc_url() {
fi
# For TypeScript, also try JavaScript offline docs (most TS keywords are JS)
if [[ "$lang" == "typescript" || "$lang" == "ts" || "$lang" == "tsx" ]]; then
if [[ $lang == "typescript" || $lang == "ts" || $lang == "tsx" ]]; then
offline_result=$(lookup_offline "$term" "js" "$import_line")
if [ -n "$offline_result" ]; then
echo "$offline_result"
@ -376,17 +376,17 @@ get_doc_url() {
# Fall back to online URLs
case "$lang" in
python|py)
python | py)
python_doc_url "$term"
;;
javascript|js|jsx)
javascript | js | jsx)
js_doc_url "$term"
;;
typescript|ts|tsx)
typescript | ts | tsx)
# For TypeScript, try JS doc first (since most keywords are shared)
# Only use TS-specific docs for TS-only features
case "$term" in
interface|type|enum|namespace|declare|readonly|abstract|implements|keyof|infer|as|is|asserts|satisfies|override|Partial|Required|Readonly|Record|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|InstanceType|Awaited)
interface | type | enum | namespace | declare | readonly | abstract | implements | keyof | infer | as | is | asserts | satisfies | override | Partial | Required | Readonly | Record | Pick | Omit | Exclude | Extract | NonNullable | ReturnType | Parameters | InstanceType | Awaited)
ts_doc_url "$term"
;;
*)
@ -397,22 +397,22 @@ get_doc_url() {
c)
c_doc_url "$term"
;;
cpp|c++|cc|cxx)
cpp | c++ | cc | cxx)
cpp_doc_url "$term"
;;
rust|rs)
rust | rs)
rust_doc_url "$term"
;;
go)
go_doc_url "$term"
;;
ruby|rb)
ruby | rb)
ruby_doc_url "$term"
;;
java)
java_doc_url "$term"
;;
shell|bash|sh)
shell | bash | sh)
shell_doc_url "$term"
;;
*)
@ -427,10 +427,10 @@ get_doc_url() {
detect_language() {
if [ -f "$RESULTS_DIR/tokei_stats.txt" ]; then
# Parse tokei output to find most used language
grep -E "^\s+(Python|JavaScript|TypeScript|C\+\+|C |Rust|Go|Ruby|Java|Shell)" "$RESULTS_DIR/tokei_stats.txt" 2>/dev/null \
| head -1 \
| awk '{print tolower($1)}' \
| sed 's/c++/cpp/'
grep -E "^\s+(Python|JavaScript|TypeScript|C\+\+|C |Rust|Go|Ruby|Java|Shell)" "$RESULTS_DIR/tokei_stats.txt" 2> /dev/null |
head -1 |
awk '{print tolower($1)}' |
sed 's/c++/cpp/'
else
echo "unknown"
fi
@ -530,7 +530,7 @@ if [ -d "$PER_LANG_DIR" ]; then
head -$TOP_N "$keyword_file" | while read -r count term; do
[ -z "$term" ] && continue
[[ "$term" =~ ^[#] ]] && continue # Skip comment lines
[[ $term =~ ^[#] ]] && continue # Skip comment lines
url=$(get_doc_url "$term" "$doc_lang")
echo "| \`$term\` | $count | [docs]($url) |" >> "$DOCS_FILE"
done
@ -568,7 +568,7 @@ if [ -d "$PER_LANG_DIR" ]; then
head -$TOP_N "$func_file" | while read -r count term; do
[ -z "$term" ] && continue
[[ "$term" =~ ^(if|for|while|switch|catch|elif)$ ]] && continue
[[ $term =~ ^(if|for|while|switch|catch|elif)$ ]] && continue
url=$(get_doc_url "$term" "$doc_lang")
echo "| \`$term()\` | $count | [docs]($url) |" >> "$DOCS_FILE"
done
@ -608,7 +608,7 @@ if [ -d "$PER_LANG_DIR" ]; then
[ -z "$import" ] && continue
# For offline lookup, pass the full import line for better context
url=$(get_doc_url "" "$doc_lang" "$import")
if [ -z "$url" ] || [[ "$url" == *"search.html"* ]]; then
if [ -z "$url" ] || [[ $url == *"search.html"* ]]; then
# Fallback: extract module and try again
module=$(echo "$import" | sed -E 's/.*[<"]([^">]+)[">].*/\1/' | sed 's|.*/||' | sed 's/\..*$//')
url=$(get_doc_url "$module" "$doc_lang")
@ -645,7 +645,7 @@ else
head -$TOP_N "$RESULTS_DIR/grep_function_calls.txt" | while read -r count term; do
[ -z "$term" ] && continue
[[ "$term" =~ ^(if|for|while|switch|catch)$ ]] && continue
[[ $term =~ ^(if|for|while|switch|catch)$ ]] && continue
url=$(get_doc_url "$term" "$PRIMARY_LANG")
echo "| \`$term()\` | $count | [docs]($url) |" >> "$DOCS_FILE"
done
@ -700,25 +700,25 @@ if [ -f "$RESULTS_DIR/grep_keywords.txt" ]; then
# Create different card types based on term type
case "$term" in
if|else|elif|elseif|switch|case|match)
if | else | elif | elseif | switch | case | match)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tConditional control flow - executes code based on boolean conditions. See: $url\t${PRIMARY_LANG}::keywords::control-flow" >> "$ANKI_FILE"
;;
for|while|loop|do|until)
for | while | loop | do | until)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tLoop construct - repeats code execution. See: $url\t${PRIMARY_LANG}::keywords::loops" >> "$ANKI_FILE"
;;
try|except|catch|finally|raise|throw)
try | except | catch | finally | raise | throw)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tException handling - manages errors and exceptional conditions. See: $url\t${PRIMARY_LANG}::keywords::exceptions" >> "$ANKI_FILE"
;;
class|struct|interface|trait|impl)
class | struct | interface | trait | impl)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tType definition - defines custom data structures. See: $url\t${PRIMARY_LANG}::keywords::types" >> "$ANKI_FILE"
;;
def|fn|func|function)
def | fn | func | function)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tFunction definition - declares a reusable block of code. See: $url\t${PRIMARY_LANG}::keywords::functions" >> "$ANKI_FILE"
;;
import|from|use|require|include)
import | from | use | require | include)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tModule import - brings external code into current scope. See: $url\t${PRIMARY_LANG}::keywords::modules" >> "$ANKI_FILE"
;;
async|await|yield)
async | await | yield)
echo -e "What is the purpose of \`$term\` in $PRIMARY_LANG?\tAsynchronous programming - handles concurrent operations. See: $url\t${PRIMARY_LANG}::keywords::async" >> "$ANKI_FILE"
;;
*)
@ -734,7 +734,7 @@ if [ -f "$RESULTS_DIR/grep_function_calls.txt" ]; then
echo "# Functions" >> "$ANKI_FILE"
head -$TOP_N "$RESULTS_DIR/grep_function_calls.txt" | while read -r count term; do
[ -z "$term" ] && continue
[[ "$term" =~ ^(if|for|while|switch|catch)$ ]] && continue
[[ $term =~ ^(if|for|while|switch|catch)$ ]] && continue
url=$(get_doc_url "$term" "$PRIMARY_LANG")
echo -e "What does \`$term()\` do in $PRIMARY_LANG? (Used $count times)\t[FILL: Look up at $url]\t${PRIMARY_LANG}::functions" >> "$ANKI_FILE"
@ -755,7 +755,7 @@ get_llm_doc_link() {
local is_import="$3" # "true" if it's an import line
# Check if it's an internal/project-specific item
if [[ "$term" =~ ^@/ ]] || [[ "$term" =~ ^\./ ]] || [[ "$term" =~ ^app\. ]] || [[ "$term" =~ ^src/ ]] || [[ "$term" =~ from\ \'@/ ]] || [[ "$term" =~ from\ \'\./ ]]; then
if [[ $term =~ ^@/ ]] || [[ $term =~ ^\./ ]] || [[ $term =~ ^app\. ]] || [[ $term =~ ^src/ ]] || [[ $term =~ from\ \'@/ ]] || [[ $term =~ from\ \'\./ ]]; then
echo "[INTERNAL - SKIP]"
return
fi
@ -763,9 +763,9 @@ get_llm_doc_link() {
# Try offline lookup
local offline_result
if [ "$is_import" = "true" ]; then
offline_result=$("$LOOKUP_SCRIPT" --import "$term" "$lang" 2>/dev/null | grep "^/" | head -1)
offline_result=$("$LOOKUP_SCRIPT" --import "$term" "$lang" 2> /dev/null | grep "^/" | head -1)
else
offline_result=$("$LOOKUP_SCRIPT" "$term" "$lang" 2>/dev/null | grep "^File:" | head -1 | sed 's/^File: //')
offline_result=$("$LOOKUP_SCRIPT" "$term" "$lang" 2> /dev/null | grep "^File:" | head -1 | sed 's/^File: //')
fi
if [ -n "$offline_result" ]; then
@ -819,7 +819,7 @@ generate_imports_with_docs() {
[ -z "$import_stmt" ] && continue
# Check if internal import
if [[ "$import_stmt" =~ @/ ]] || [[ "$import_stmt" =~ \'\./ ]] || [[ "$import_stmt" =~ from\ app\. ]] || [[ "$import_stmt" =~ from\ src\. ]]; then
if [[ $import_stmt =~ @/ ]] || [[ $import_stmt =~ \'\./ ]] || [[ $import_stmt =~ from\ app\. ]] || [[ $import_stmt =~ from\ src\. ]]; then
echo "$count $import_stmt → [INTERNAL - SKIP]"
else
local doc_link=$(get_llm_doc_link "$import_stmt" "$PRIMARY_LANG" "true")
@ -962,7 +962,6 @@ PROMPT_FOOTER
echo -e "${GREEN}Created: $LLM_PROMPT_FILE${NC}"
#==============================================================================
# Summary
#==============================================================================

View File

@ -15,7 +15,7 @@ DEFAULT_RESOLUTION="320x240"
# Function to display usage
usage() {
cat <<EOF
cat << EOF
Usage: $0 <input_image> [resolution] [output_image]
Arguments:

View File

@ -34,9 +34,9 @@ echo ""
# Install Exercism CLI
install_exercism_cli() {
if command -v exercism &>/dev/null; then
if command -v exercism &> /dev/null; then
local version
version=$(exercism version 2>/dev/null | head -1)
version=$(exercism version 2> /dev/null | head -1)
success "Exercism CLI already installed: $version"
return 0
fi
@ -44,18 +44,18 @@ install_exercism_cli() {
echo "Installing Exercism CLI..."
# Try package managers first
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
# Check AUR
if command -v yay &>/dev/null; then
if command -v yay &> /dev/null; then
yay -S --noconfirm exercism-bin
success "Exercism CLI installed via AUR"
return 0
elif command -v paru &>/dev/null; then
elif command -v paru &> /dev/null; then
paru -S --noconfirm exercism-bin
success "Exercism CLI installed via AUR"
return 0
fi
elif command -v brew &>/dev/null; then
elif command -v brew &> /dev/null; then
brew install exercism
success "Exercism CLI installed via Homebrew"
return 0
@ -85,7 +85,7 @@ install_exercism_cli() {
download_url=$(curl -fsSL "$latest_url" | grep "browser_download_url.*${os}-${arch}" | head -1 | cut -d '"' -f 4)
if [[ -z "$download_url" ]]; then
if [[ -z $download_url ]]; then
error "Could not find download URL for your system"
echo "Please install manually from: https://exercism.org/docs/using/solving-exercises/working-locally"
return 1
@ -146,17 +146,17 @@ install_test_runners() {
echo ""
# Python - pytest
if command -v python3 &>/dev/null; then
if python3 -c "import pytest" 2>/dev/null; then
if command -v python3 &> /dev/null; then
if python3 -c "import pytest" 2> /dev/null; then
success "Python: pytest already installed"
else
info "Installing pytest for Python exercises..."
pip3 install --user pytest 2>/dev/null && success "Python: pytest installed" || warn "Python: install pytest manually"
pip3 install --user pytest 2> /dev/null && success "Python: pytest installed" || warn "Python: install pytest manually"
fi
fi
# JavaScript/TypeScript - Node.js + npm
if command -v node &>/dev/null; then
if command -v node &> /dev/null; then
success "JavaScript/TypeScript: Node.js available ($(node --version))"
info " Tests run with: npm test (or jest)"
else
@ -164,7 +164,7 @@ install_test_runners() {
fi
# C - gcc + criterion/cmocka
if command -v gcc &>/dev/null; then
if command -v gcc &> /dev/null; then
success "C: gcc available"
info " Some C exercises use Unity test framework (included in exercise)"
else
@ -172,7 +172,7 @@ install_test_runners() {
fi
# C++ - g++ + Catch2/doctest
if command -v g++ &>/dev/null; then
if command -v g++ &> /dev/null; then
success "C++: g++ available"
info " C++ exercises use Catch2 (header-only, included in exercise)"
else
@ -180,12 +180,12 @@ install_test_runners() {
fi
# Rust
if command -v cargo &>/dev/null; then
if command -v cargo &> /dev/null; then
success "Rust: cargo available (tests with: cargo test)"
fi
# Go
if command -v go &>/dev/null; then
if command -v go &> /dev/null; then
success "Go: go available (tests with: go test)"
fi
}
@ -200,10 +200,10 @@ download_track() {
# Get list of exercises
local exercises
exercises=$(curl -fsSL "https://exercism.org/api/v2/tracks/${track}/exercises" 2>/dev/null |
exercises=$(curl -fsSL "https://exercism.org/api/v2/tracks/${track}/exercises" 2> /dev/null |
grep -oP '"slug":"\K[^"]+' | head -n "$count")
if [[ -z "$exercises" ]]; then
if [[ -z $exercises ]]; then
warn "Could not fetch exercise list for $track"
return 1
fi
@ -211,10 +211,10 @@ download_track() {
local downloaded=0
for exercise in $exercises; do
local exercise_dir="$EXERCISM_DIR/$track/$exercise"
if [[ -d "$exercise_dir" ]]; then
if [[ -d $exercise_dir ]]; then
echo " [exists] $exercise"
else
if exercism download --track="$track" --exercise="$exercise" 2>/dev/null; then
if exercism download --track="$track" --exercise="$exercise" 2> /dev/null; then
echo " [downloaded] $exercise"
((downloaded++))
else
@ -256,8 +256,8 @@ show_usage() {
echo ""
echo -e "${CYAN}Batch download (requires API token):${NC}"
echo " # Download first 20 Python exercises:"
echo " for ex in \$(exercism download --track=python 2>&1 | head -20); do"
echo " exercism download --track=python --exercise=\$ex"
echo ' for ex in $(exercism download --track=python 2>&1 | head -20); do'
echo ' exercism download --track=python --exercise=$ex'
echo " done"
echo ""
echo "Exercises are in: $EXERCISM_DIR"
@ -291,10 +291,10 @@ main() {
for track in "${tracks[@]}"; do
local exercise_dir="$EXERCISM_DIR/$track/hello-world"
if [[ -d "$exercise_dir" ]]; then
if [[ -d $exercise_dir ]]; then
echo " [$track] hello-world already exists"
else
if exercism download --track="$track" --exercise="hello-world" 2>/dev/null; then
if exercism download --track="$track" --exercise="hello-world" 2> /dev/null; then
success "[$track] hello-world downloaded"
else
warn "[$track] hello-world requires authentication"

View File

@ -27,27 +27,27 @@ echo ""
# Detect package manager and install Zeal
install_zeal() {
if command -v zeal &>/dev/null; then
if command -v zeal &> /dev/null; then
success "Zeal is already installed"
return 0
fi
echo "Installing Zeal offline documentation browser..."
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
# Arch Linux
sudo pacman -S --noconfirm zeal
elif command -v apt &>/dev/null; then
elif command -v apt &> /dev/null; then
# Debian/Ubuntu
sudo apt update
sudo apt install -y zeal
elif command -v dnf &>/dev/null; then
elif command -v dnf &> /dev/null; then
# Fedora
sudo dnf install -y zeal
elif command -v zypper &>/dev/null; then
elif command -v zypper &> /dev/null; then
# openSUSE
sudo zypper install -y zeal
elif command -v flatpak &>/dev/null; then
elif command -v flatpak &> /dev/null; then
# Flatpak fallback
flatpak install -y flathub org.zealdocs.Zeal
else
@ -64,7 +64,7 @@ get_docsets_dir() {
local docsets_dir
# Check if using Flatpak
if command -v flatpak &>/dev/null && flatpak list | grep -q "org.zealdocs.Zeal"; then
if command -v flatpak &> /dev/null && flatpak list | grep -q "org.zealdocs.Zeal"; then
docsets_dir="$HOME/.var/app/org.zealdocs.Zeal/data/Zeal/Zeal/docsets"
else
# Standard installation
@ -184,7 +184,7 @@ main() {
# Ask about extras
echo ""
read -r -p "Install additional docsets (Bash, HTML, CSS, NodeJS)? [Y/n] " response
if [[ ! "$response" =~ ^[Nn]$ ]]; then
if [[ ! $response =~ ^[Nn]$ ]]; then
for docset in "${extras[@]}"; do
download_docset "$docset" "$docsets_dir"
done
@ -198,7 +198,7 @@ main() {
echo ""
echo "Installed documentation:"
for f in "$docsets_dir"/*.docset; do
if [[ -d "$f" ]]; then
if [[ -d $f ]]; then
echo "$(basename "$f" .docset)"
fi
done
@ -219,8 +219,8 @@ main() {
# Offer to launch Zeal
read -r -p "Launch Zeal now? [y/N] " response
if [[ "$response" =~ ^[Yy]$ ]]; then
nohup zeal &>/dev/null &
if [[ $response =~ ^[Yy]$ ]]; then
nohup zeal &> /dev/null &
success "Zeal launched"
fi
}

View File

@ -39,7 +39,7 @@ echo ""
echo "=== 1. Installing Python NLP-based Plagiarism Tools ==="
# Check for Python 3
if ! command -v python3 &>/dev/null; then
if ! command -v python3 &> /dev/null; then
error "Python 3 is required but not installed."
exit 1
fi
@ -90,11 +90,11 @@ success "NLTK data downloaded"
# Download spaCy English model (small)
echo "Downloading spaCy English model..."
python3 -m spacy download en_core_web_sm 2>/dev/null || warn "spaCy model download may need manual install: python -m spacy download en_core_web_sm"
python3 -m spacy download en_core_web_sm 2> /dev/null || warn "spaCy model download may need manual install: python -m spacy download en_core_web_sm"
success "spaCy model installed"
# Create a simple plagiarism checker script
cat >"$INSTALL_DIR/check_plagiarism.py" <<'PYEOF'
cat > "$INSTALL_DIR/check_plagiarism.py" << 'PYEOF'
#!/usr/bin/env python3
"""
Simple Text Plagiarism Checker
@ -325,7 +325,7 @@ success "Created plagiarism checker script at $INSTALL_DIR/check_plagiarism.py"
# Create convenience wrapper
mkdir -p "$HOME/.local/bin"
cat >"$HOME/.local/bin/plagcheck" <<WRAPEOF
cat > "$HOME/.local/bin/plagcheck" << WRAPEOF
#!/usr/bin/env bash
# Wrapper for plagiarism checker
source "$VENV_DIR/bin/activate"
@ -345,13 +345,13 @@ echo "=== 2. Installing Sherlock Text Plagiarism Detector ==="
SHERLOCK_DIR="$INSTALL_DIR/sherlock"
if [ ! -d "$SHERLOCK_DIR" ]; then
# There are several Sherlock implementations; using a popular Python one
if command -v git &>/dev/null; then
if command -v git &> /dev/null; then
# Clone a text-based similarity tool
git clone --depth 1 https://github.com/Zedeldi/sherlock-py.git "$SHERLOCK_DIR" 2>/dev/null || {
git clone --depth 1 https://github.com/Zedeldi/sherlock-py.git "$SHERLOCK_DIR" 2> /dev/null || {
warn "Could not clone sherlock-py, trying alternative..."
# Alternative: Create a simple n-gram based sherlock
mkdir -p "$SHERLOCK_DIR"
cat >"$SHERLOCK_DIR/sherlock.py" <<'SHERLOCKEOF'
cat > "$SHERLOCK_DIR/sherlock.py" << 'SHERLOCKEOF'
#!/usr/bin/env python3
"""
Sherlock - Simple text plagiarism detector using n-gram fingerprinting.
@ -459,7 +459,7 @@ fi
echo ""
echo "=== 3. Checking for Ferret (Java-based plagiarism tool) ==="
if command -v java &>/dev/null; then
if command -v java &> /dev/null; then
FERRET_DIR="$INSTALL_DIR/ferret"
if [ ! -d "$FERRET_DIR" ]; then
mkdir -p "$FERRET_DIR"
@ -478,7 +478,7 @@ fi
echo ""
echo "=== 4. WCopyfind Information (Windows tool, needs Wine) ==="
if command -v wine &>/dev/null; then
if command -v wine &> /dev/null; then
echo "Wine is available. WCopyfind can be run via Wine."
echo "Download from: https://plagiarism.bloomfieldmedia.com/software/wcopyfind/"
echo "Run with: wine /path/to/WCopyfind.exe"

View File

@ -57,7 +57,7 @@ lookup_python() {
if [ -f "$doc_dir/library/${module_lower}.html" ]; then
# Find anchor for the specific item in the module
local anchor
anchor=$(grep -oP "id=\"[^\"]*${term}[^\"]*\"" "$doc_dir/library/${module_lower}.html" 2>/dev/null | head -1 | sed 's/id="//;s/"//')
anchor=$(grep -oP "id=\"[^\"]*${term}[^\"]*\"" "$doc_dir/library/${module_lower}.html" 2> /dev/null | head -1 | sed 's/id="//;s/"//')
if [ -n "$anchor" ]; then
result="$doc_dir/library/${module_lower}.html#$anchor"
@ -78,7 +78,7 @@ lookup_python() {
# Compound statements (reference/compound_stmts.html)
case "$term_lower" in
if|elif|else)
if | elif | else)
result="$doc_dir/reference/compound_stmts.html#if"
desc="Python: if statement"
;;
@ -98,7 +98,7 @@ lookup_python() {
result="$doc_dir/reference/compound_stmts.html#class"
desc="Python: class definition"
;;
try|except|finally)
try | except | finally)
result="$doc_dir/reference/compound_stmts.html#try"
desc="Python: try statement"
;;
@ -110,7 +110,7 @@ lookup_python() {
result="$doc_dir/reference/compound_stmts.html#async"
desc="Python: async definition"
;;
match|case)
match | case)
result="$doc_dir/reference/compound_stmts.html#match"
desc="Python: match statement"
;;
@ -135,7 +135,7 @@ lookup_python() {
result="$doc_dir/reference/simple_stmts.html#continue"
desc="Python: continue statement"
;;
import|from)
import | from)
result="$doc_dir/reference/simple_stmts.html#import"
desc="Python: import statement"
;;
@ -207,7 +207,7 @@ lookup_python() {
# Built-in constants (library/constants.html) - case-sensitive!
if [ -z "$result" ]; then
case "$term" in
True|False)
True | False)
result="$doc_dir/library/constants.html#$term"
desc="Python: $term constant"
;;
@ -244,7 +244,7 @@ lookup_python() {
# PRIORITY 3: Built-in functions (library/functions.html)
#--------------------------------------------------------------------------
if [ -z "$result" ] && [ -f "$doc_dir/library/functions.html" ]; then
if grep -q "id=\"$term_lower\"" "$doc_dir/library/functions.html" 2>/dev/null; then
if grep -q "id=\"$term_lower\"" "$doc_dir/library/functions.html" 2> /dev/null; then
result="$doc_dir/library/functions.html#$term_lower"
desc="Python built-in function: $term"
fi
@ -255,11 +255,11 @@ lookup_python() {
#--------------------------------------------------------------------------
if [ -z "$result" ]; then
case "$term_lower" in
str|string)
str | string)
result="$doc_dir/library/stdtypes.html#str"
desc="Python: str type"
;;
int|integer)
int | integer)
result="$doc_dir/library/stdtypes.html#int"
desc="Python: int type"
;;
@ -271,7 +271,7 @@ lookup_python() {
result="$doc_dir/library/stdtypes.html#list"
desc="Python: list type"
;;
dict|dictionary)
dict | dictionary)
result="$doc_dir/library/stdtypes.html#dict"
desc="Python: dict type"
;;
@ -283,7 +283,7 @@ lookup_python() {
result="$doc_dir/library/stdtypes.html#tuple"
desc="Python: tuple type"
;;
bool|boolean)
bool | boolean)
result="$doc_dir/library/stdtypes.html#boolean-values"
desc="Python: bool type"
;;
@ -300,7 +300,7 @@ lookup_python() {
if [ -z "$result" ]; then
local found_in
# Look for exact id match first
found_in=$(grep -l "id=\"$term\"" "$doc_dir/library/"*.html 2>/dev/null | head -1)
found_in=$(grep -l "id=\"$term\"" "$doc_dir/library/"*.html 2> /dev/null | head -1)
if [ -n "$found_in" ]; then
result="$found_in#$term"
local module
@ -314,7 +314,7 @@ lookup_python() {
#--------------------------------------------------------------------------
if [ -z "$result" ] && [ -f "$INDEX_DIR/python_index.txt" ]; then
local index_match
index_match=$(grep -i "^$term " "$INDEX_DIR/python_index.txt" 2>/dev/null | head -1)
index_match=$(grep -i "^$term " "$INDEX_DIR/python_index.txt" 2> /dev/null | head -1)
if [ -n "$index_match" ]; then
result=$(echo "$index_match" | cut -d' ' -f2-)
desc="Python: $term (from index)"
@ -343,21 +343,21 @@ lookup_cpp() {
# Common C headers
case "$term" in
stdio.h|stdio)
stdio.h | stdio)
[ -f "$doc_dir/reference/cstdio/index.html" ] && result="$doc_dir/reference/cstdio/index.html"
[ -f "$doc_dir/en/c/io.html" ] && result="$doc_dir/en/c/io.html"
desc="C standard I/O header"
;;
stdlib.h|stdlib)
stdlib.h | stdlib)
[ -f "$doc_dir/reference/cstdlib/index.html" ] && result="$doc_dir/reference/cstdlib/index.html"
[ -f "$doc_dir/en/c/memory.html" ] && result="$doc_dir/en/c/memory.html"
desc="C standard library header"
;;
string.h|cstring)
string.h | cstring)
[ -f "$doc_dir/reference/cstring/index.html" ] && result="$doc_dir/reference/cstring/index.html"
desc="C string handling header"
;;
math.h|cmath)
math.h | cmath)
[ -f "$doc_dir/reference/cmath/index.html" ] && result="$doc_dir/reference/cmath/index.html"
desc="C math header"
;;
@ -386,16 +386,16 @@ lookup_cpp() {
# C keywords
case "$term" in
if|else|for|while|do|switch|case|break|continue|return|goto)
if | else | for | while | do | switch | case | break | continue | return | goto)
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
[ -f "$doc_dir/en/cpp/language/$term.html" ] && result="$doc_dir/en/cpp/language/$term.html"
desc="C/C++ keyword: $term"
;;
int|char|float|double|void|long|short|unsigned|signed)
int | char | float | double | void | long | short | unsigned | signed)
[ -f "$doc_dir/en/c/language/type.html" ] && result="$doc_dir/en/c/language/type.html"
desc="C/C++ type: $term"
;;
struct|union|enum|typedef)
struct | union | enum | typedef)
[ -f "$doc_dir/en/c/language/$term.html" ] && result="$doc_dir/en/c/language/$term.html"
desc="C/C++ keyword: $term"
;;
@ -404,7 +404,7 @@ lookup_cpp() {
# Search in files if not found (use -L to follow symlinks)
if [ -z "$result" ]; then
local found
found=$(find -L "$doc_dir" -name "*${term}*" -type f 2>/dev/null | head -1)
found=$(find -L "$doc_dir" -name "*${term}*" -type f 2> /dev/null | head -1)
if [ -n "$found" ]; then
result="$found"
desc="C/C++: $term"
@ -450,7 +450,7 @@ lookup_js() {
local stmt_dir="$mdn_dir/web/javascript/reference/statements/$value"
if [ -d "$stmt_dir" ] && [ -f "$stmt_dir/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$stmt_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$stmt_dir/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$stmt_dir/index.md|${title:-$term}"
return 0
fi
@ -459,7 +459,7 @@ lookup_js() {
# Handle boolean/null literals
case "$term_lower" in
true|false)
true | false)
local bool_dir="$mdn_dir/web/javascript/reference/global_objects/boolean"
if [ -d "$bool_dir" ] && [ -f "$bool_dir/index.md" ]; then
echo "$bool_dir/index.md|Boolean ($term)"
@ -470,7 +470,7 @@ lookup_js() {
local null_dir="$mdn_dir/web/javascript/reference/operators/null"
if [ -d "$null_dir" ] && [ -f "$null_dir/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$null_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$null_dir/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$null_dir/index.md|${title:-null}"
return 0
fi
@ -479,7 +479,7 @@ lookup_js() {
local undef_dir="$mdn_dir/web/javascript/reference/global_objects/undefined"
if [ -d "$undef_dir" ] && [ -f "$undef_dir/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$undef_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$undef_dir/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$undef_dir/index.md|${title:-undefined}"
return 0
fi
@ -499,10 +499,10 @@ lookup_js() {
if [ -d "$search_dir" ]; then
# Look for exact directory match (MDN uses directories with index.md)
local found_dir
found_dir=$(find "$search_dir" -maxdepth 2 -type d -iname "$term" 2>/dev/null | head -1)
found_dir=$(find "$search_dir" -maxdepth 2 -type d -iname "$term" 2> /dev/null | head -1)
if [ -n "$found_dir" ] && [ -f "$found_dir/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$found_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$found_dir/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$found_dir/index.md|${title:-$term}"
return 0
fi
@ -515,17 +515,17 @@ lookup_js() {
local api_dir="$mdn_dir/web/api/${term_lower}_api"
if [ -d "$api_dir" ] && [ -f "$api_dir/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$api_dir/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$api_dir/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$api_dir/index.md|${title:-$term API}"
return 0
fi
# Then try exact top-level API interface (e.g., Console, Document, Element)
local found
found=$(find "$mdn_dir/web/api" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
found=$(find "$mdn_dir/web/api" -maxdepth 1 -type d -iname "$term" 2> /dev/null | head -1)
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$found/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$found/index.md|${title:-$term}"
return 0
fi
@ -534,16 +534,16 @@ lookup_js() {
local window_method="$mdn_dir/web/api/window/${term_lower}"
if [ -d "$window_method" ] && [ -f "$window_method/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$window_method/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$window_method/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$window_method/index.md|${title:-Window.$term()}"
return 0
fi
# Search nested API methods
found=$(find "$mdn_dir/web/api" -maxdepth 3 -type d -iname "$term" 2>/dev/null | head -1)
found=$(find "$mdn_dir/web/api" -maxdepth 3 -type d -iname "$term" 2> /dev/null | head -1)
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$found/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$found/index.md|${title:-$term}"
return 0
fi
@ -552,10 +552,10 @@ lookup_js() {
# Now try partial matches in Global Objects (e.g., Array.from, Object.keys)
if [ -d "$mdn_dir/web/javascript/reference/global_objects" ]; then
local found
found=$(find "$mdn_dir/web/javascript/reference/global_objects" -maxdepth 2 -type d -iname "*${term}*" 2>/dev/null | head -1)
found=$(find "$mdn_dir/web/javascript/reference/global_objects" -maxdepth 2 -type d -iname "*${term}*" 2> /dev/null | head -1)
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$found/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$found/index.md|${title:-$term}"
return 0
fi
@ -564,10 +564,10 @@ lookup_js() {
# Glossary as last resort
if [ -d "$mdn_dir/glossary" ]; then
local found
found=$(find "$mdn_dir/glossary" -maxdepth 1 -type d -iname "$term" 2>/dev/null | head -1)
found=$(find "$mdn_dir/glossary" -maxdepth 1 -type d -iname "$term" 2> /dev/null | head -1)
if [ -n "$found" ] && [ -f "$found/index.md" ]; then
local title
title=$(grep -m1 "^title:" "$found/index.md" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$found/index.md" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "$found/index.md|${title:-$term}"
return 0
fi
@ -584,15 +584,15 @@ lookup_rust() {
local result=""
local desc=""
if command -v rustup &>/dev/null; then
if command -v rustup &> /dev/null; then
# Use rustup doc to get path
local rust_doc_path
rust_doc_path=$(rustup doc --path 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
rust_doc_path=$(rustup doc --path 2> /dev/null | head -1 | xargs dirname 2> /dev/null)
# Search in std docs
if [ -d "$rust_doc_path/std" ]; then
local found
found=$(find "$rust_doc_path/std" -name "*${term}*" -type f 2>/dev/null | head -1)
found=$(find "$rust_doc_path/std" -name "*${term}*" -type f 2> /dev/null | head -1)
if [ -n "$found" ]; then
result="$found"
desc="Rust: $term"
@ -613,9 +613,9 @@ lookup_go() {
local result=""
local desc=""
if command -v go &>/dev/null; then
if command -v go &> /dev/null; then
# Check if it's a stdlib package
if go doc "$term" &>/dev/null; then
if go doc "$term" &> /dev/null; then
result="go doc $term"
desc="Go package: $term (use 'go doc $term' to view)"
fi
@ -637,7 +637,7 @@ lookup_shell() {
# Check bash builtins
if [ -f "$doc_dir/bash_builtins.txt" ]; then
if grep -q "=== $term ===" "$doc_dir/bash_builtins.txt" 2>/dev/null; then
if grep -q "=== $term ===" "$doc_dir/bash_builtins.txt" 2> /dev/null; then
result="$doc_dir/bash_builtins.txt"
desc="Bash builtin: $term"
fi
@ -645,7 +645,7 @@ lookup_shell() {
# Check common commands
if [ -z "$result" ] && [ -f "$doc_dir/common_commands.txt" ]; then
if grep -q "^$term" "$doc_dir/common_commands.txt" 2>/dev/null; then
if grep -q "^$term" "$doc_dir/common_commands.txt" 2> /dev/null; then
local cmd_desc
cmd_desc=$(grep "^$term" "$doc_dir/common_commands.txt" | head -1)
result="$doc_dir/common_commands.txt"
@ -656,7 +656,7 @@ lookup_shell() {
# Try man page
if [ -z "$result" ]; then
local man_path
man_path=$(man -w "$term" 2>/dev/null)
man_path=$(man -w "$term" 2> /dev/null)
if [ -n "$man_path" ]; then
result="man $term"
desc="Manual page: $term (use 'man $term' to view)"
@ -677,7 +677,7 @@ lookup_all() {
# Try each language
for lang in python cpp js rust go shell; do
local result
result=$(lookup_$lang "$term" 2>/dev/null)
result=$(lookup_$lang "$term" 2> /dev/null)
if [ -n "$result" ]; then
echo "$lang: $result"
fi
@ -691,7 +691,7 @@ parse_python_import() {
local import_line="$1"
# Handle "from X import Y" format
if [[ "$import_line" =~ ^from[[:space:]]+([^[:space:]]+)[[:space:]]+import[[:space:]]+(.+) ]]; then
if [[ $import_line =~ ^from[[:space:]]+([^[:space:]]+)[[:space:]]+import[[:space:]]+(.+) ]]; then
local module="${BASH_REMATCH[1]}"
local items="${BASH_REMATCH[2]}"
@ -704,7 +704,7 @@ parse_python_import() {
fi
# Handle "import X" format
if [[ "$import_line" =~ ^import[[:space:]]+([^[:space:],]+) ]]; then
if [[ $import_line =~ ^import[[:space:]]+([^[:space:],]+) ]]; then
local module="${BASH_REMATCH[1]}"
echo "$module|"
return 0
@ -752,7 +752,7 @@ lookup_import() {
lookup_cpp "$header"
;;
javascript|typescript)
javascript | typescript)
# Extract module from import/require
local module=""
# Match: from "module" or from 'module'
@ -778,19 +778,19 @@ extract_doc_content() {
local term="$2"
local max_lines="${3:-20}"
if [[ "$file" == *.html ]]; then
if [[ $file == *.html ]]; then
# Extract text from HTML, find section about term
if command -v html2text &>/dev/null; then
html2text "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
elif command -v lynx &>/dev/null; then
lynx -dump -nolist "$file" 2>/dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
if command -v html2text &> /dev/null; then
html2text "$file" 2> /dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
elif command -v lynx &> /dev/null; then
lynx -dump -nolist "$file" 2> /dev/null | grep -A"$max_lines" -i "$term" | head -"$max_lines"
else
# Basic extraction
sed 's/<[^>]*>//g' "$file" | grep -A"$max_lines" -i "$term" | head -"$max_lines"
fi
elif [[ "$file" == *.json ]]; then
elif [[ $file == *.json ]]; then
# Pretty print JSON section
grep -A5 "\"$term\"" "$file" 2>/dev/null
grep -A5 "\"$term\"" "$file" 2> /dev/null
else
# Plain text
grep -A"$max_lines" -i "$term" "$file" | head -"$max_lines"
@ -857,11 +857,11 @@ main() {
term="$1" # This is the file
shift
;;
--help|-h)
--help | -h)
usage
exit 0
;;
python|cpp|c_cpp|c|js|javascript|ts|typescript|tsx|jsx|rust|go|shell|bash|all)
python | cpp | c_cpp | c | js | javascript | ts | typescript | tsx | jsx | rust | go | shell | bash | all)
lang="$1"
shift
;;
@ -877,7 +877,7 @@ main() {
# Normalize language
case "$lang" in
c) lang="cpp" ;;
javascript|js|typescript|ts|jsx|tsx) lang="js" ;;
javascript | js | typescript | ts | jsx | tsx) lang="js" ;;
bash) lang="shell" ;;
"") lang="all" ;;
esac
@ -887,7 +887,7 @@ main() {
if [ "$lang" = "all" ]; then
lookup_all "$term"
else
result=$(lookup_$lang "$term" 2>/dev/null)
result=$(lookup_$lang "$term" 2> /dev/null)
if [ -n "$result" ]; then
local file desc
file=$(echo "$result" | cut -d'|' -f1)
@ -903,7 +903,7 @@ main() {
fi
if $open_file && [ -f "$file" ]; then
xdg-open "$file" 2>/dev/null &
xdg-open "$file" 2> /dev/null &
fi
else
echo -e "${RED}Not found:${NC} $term in $lang documentation"
@ -929,7 +929,7 @@ main() {
while IFS= read -r line || [ -n "$line" ]; do
[ -z "$line" ] && continue
[[ "$line" =~ ^# ]] && continue
[[ $line =~ ^# ]] && continue
echo -e "${CYAN}Looking up:${NC} $line"
lookup_import "$line" "$lang"

View File

@ -17,7 +17,7 @@ OUTPUT_FORMAT="jpg"
PDF_FILES=()
usage() {
cat <<EOF
cat << EOF
Usage:
$(basename "$0") [OPTIONS] PDF_FILE [PDF_FILE...]

View File

@ -119,7 +119,7 @@ check_dependencies() {
# Check for basic tools
for cmd in git curl grep sed awk; do
if ! command -v "$cmd" &>/dev/null; then
if ! command -v "$cmd" &> /dev/null; then
missing+=("$cmd")
fi
done
@ -160,7 +160,7 @@ get_repo() {
local repo_dir=""
# Check if it's a URL (git clone needed)
if [[ "$input" =~ ^https?:// ]] || [[ "$input" =~ ^git@ ]]; then
if [[ $input =~ ^https?:// ]] || [[ $input =~ ^git@ ]]; then
print_step "Cloning repository..."
# Extract repo name from URL
@ -229,7 +229,7 @@ generate_materials() {
# Run study materials generator
cd "$analysis_dir"
if "$STUDY_SCRIPT" . 2>/dev/null | grep -E "^(Created|✓|Files created)" | head -5; then
if "$STUDY_SCRIPT" . 2> /dev/null | grep -E "^(Created|✓|Files created)" | head -5; then
print_success "Study materials generated"
else
# Try anyway, might have succeeded
@ -275,7 +275,7 @@ show_summary() {
if [ -f "$output_dir/anki_cards.txt" ]; then
local card_count
card_count=$(grep -c $'^\w' "$output_dir/anki_cards.txt" 2>/dev/null || echo "0")
card_count=$(grep -c $'^\w' "$output_dir/anki_cards.txt" 2> /dev/null || echo "0")
echo -e " 🎴 ${GREEN}anki_cards.txt${NC} (~$card_count cards)"
echo " Import to Anki: File → Import → Tab separated"
fi
@ -293,8 +293,8 @@ show_summary() {
echo ""
echo -e "${BOLD}Quick preview of imports with offline docs:${NC}"
if [ -f "$output_dir/documentation_links.md" ]; then
grep -A20 "import/from" "$output_dir/documentation_links.md" 2>/dev/null | \
grep "^\| \`" | head -5 | \
grep -A20 "import/from" "$output_dir/documentation_links.md" 2> /dev/null |
grep "^\| \`" | head -5 |
sed 's/|/│/g'
fi
@ -339,7 +339,7 @@ main() {
# Set default output dir based on repo name
if [ -z "$output_dir" ]; then
output_dir="$STUDY_MATERIALS_BASE/$REPO_NAME"
elif [[ "$output_dir" != /* ]]; then
elif [[ $output_dir != /* ]]; then
# Convert relative to absolute
output_dir="$(pwd)/$output_dir"
fi

View File

@ -40,7 +40,7 @@ log() {
local msg="$1"
echo -e "${GREEN}[$(timestamp)]${NC} $msg"
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e $LOG_FILE && -w /var/log ]]; then
echo "[$(timestamp)] $msg" >>"$LOG_FILE" 2>&1 || true
echo "[$(timestamp)] $msg" >> "$LOG_FILE" 2>&1 || true
fi
}
@ -48,7 +48,7 @@ warn() {
local msg="$1"
echo -e "${YELLOW}[WARN]${NC} $msg" >&2
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e $LOG_FILE && -w /var/log ]]; then
echo "[$(timestamp)] [WARN] $msg" >>"$LOG_FILE" 2>&1 || true
echo "[$(timestamp)] [WARN] $msg" >> "$LOG_FILE" 2>&1 || true
fi
}
@ -56,7 +56,7 @@ error() {
local msg="$1"
echo -e "${RED}[ERROR]${NC} $msg" >&2
if [[ -w "$(dirname "$LOG_FILE")" ]] || [[ ! -e $LOG_FILE && -w /var/log ]]; then
echo "[$(timestamp)] [ERROR] $msg" >>"$LOG_FILE" 2>&1 || true
echo "[$(timestamp)] [ERROR] $msg" >> "$LOG_FILE" 2>&1 || true
fi
}
@ -90,7 +90,7 @@ require_non_root() {
}
usage() {
cat <<EOF
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS] [COMMAND]
Root BL9000 phone from Arch Linux using Magisk.
@ -138,49 +138,49 @@ install_dependencies() {
local missing=()
# Check for required commands
if ! command -v adb >/dev/null 2>&1; then
if ! command -v adb > /dev/null 2>&1; then
packages+=("android-tools")
missing+=("adb")
fi
if ! command -v fastboot >/dev/null 2>&1 && ! pacman -Q android-tools >/dev/null 2>&1; then
if ! command -v fastboot > /dev/null 2>&1 && ! pacman -Q android-tools > /dev/null 2>&1; then
packages+=("android-tools")
missing+=("fastboot")
fi
if ! command -v unzip >/dev/null 2>&1; then
if ! command -v unzip > /dev/null 2>&1; then
packages+=("unzip")
missing+=("unzip")
fi
if ! command -v curl >/dev/null 2>&1; then
if ! command -v curl > /dev/null 2>&1; then
packages+=("curl")
missing+=("curl")
fi
if ! command -v python3 >/dev/null 2>&1; then
if ! command -v python3 > /dev/null 2>&1; then
packages+=("python")
missing+=("python3")
fi
if ! command -v git >/dev/null 2>&1; then
if ! command -v git > /dev/null 2>&1; then
packages+=("git")
missing+=("git")
fi
# Check for libusb and fuse2 (needed for mtkclient)
if ! pacman -Q libusb >/dev/null 2>&1; then
if ! pacman -Q libusb > /dev/null 2>&1; then
packages+=("libusb")
missing+=("libusb")
fi
if ! pacman -Q fuse2 >/dev/null 2>&1; then
if ! pacman -Q fuse2 > /dev/null 2>&1; then
packages+=("fuse2")
missing+=("fuse2")
fi
# Check for python-protobuf (needed for boot image tools)
if ! python3 -c "import google.protobuf" 2>/dev/null; then
if ! python3 -c "import google.protobuf" 2> /dev/null; then
packages+=("python-protobuf")
missing+=("python-protobuf")
fi
@ -215,7 +215,7 @@ setup_udev_rules() {
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
log "Installing MTKClient udev rules..."
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"
sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2> /dev/null || warn "Failed to copy MTKClient rules"
fi
fi
@ -230,7 +230,7 @@ setup_udev_rules() {
log "Creating Android udev rules..."
# Create comprehensive udev rules for Android devices
sudo tee "$udev_file" >/dev/null <<'EOF'
sudo tee "$udev_file" > /dev/null << 'EOF'
# Android Debug Bridge (ADB) devices
# Add your device's vendor ID if not listed
@ -244,7 +244,7 @@ EOF
fi
# Create adbusers group if it doesn't exist
if ! getent group adbusers >/dev/null; then
if ! getent group adbusers > /dev/null; then
sudo groupadd -r adbusers
log "Created adbusers group"
fi
@ -255,7 +255,7 @@ EOF
log "Added $USER to adbusers group"
fi
if ! getent group plugdev >/dev/null; then
if ! getent group plugdev > /dev/null; then
sudo groupadd -r plugdev
fi
@ -264,7 +264,7 @@ EOF
log "Added $USER to plugdev group"
fi
if ! getent group dialout >/dev/null; then
if ! getent group dialout > /dev/null; then
sudo groupadd -r dialout
fi
@ -290,7 +290,7 @@ backup_device_data() {
mkdir -p "$backup_dir"
log "Backup directory: $backup_dir" # Check device connection first
if ! adb get-state >/dev/null 2>&1; then
if ! adb get-state > /dev/null 2>&1; then
error "Device not connected. Please connect your device first."
return 1
fi
@ -302,7 +302,7 @@ backup_device_data() {
local storage_dirs=("DCIM" "Pictures" "Documents" "Download" "Music" "Movies" "WhatsApp" "Telegram")
for dir in "${storage_dirs[@]}"; do
if adb shell "[ -d /sdcard/$dir ]" 2>/dev/null; then
if adb shell "[ -d /sdcard/$dir ]" 2> /dev/null; then
log " → Backing up /sdcard/$dir..."
if adb pull "/sdcard/$dir" "$backup_dir/$dir" 2>&1 | grep -v "^$"; then
log "$dir backed up successfully"
@ -314,34 +314,34 @@ backup_device_data() {
# 2. Backup SMS/MMS (if possible)
log "Backing up SMS/MMS database..."
if adb shell "su -c 'cp /data/data/com.android.providers.telephony/databases/mmssms.db /sdcard/mmssms.db'" 2>/dev/null; then
adb pull /sdcard/mmssms.db "$backup_dir/mmssms.db" 2>/dev/null && log " ✓ SMS/MMS backed up"
adb shell "rm /sdcard/mmssms.db" 2>/dev/null || true
if adb shell "su -c 'cp /data/data/com.android.providers.telephony/databases/mmssms.db /sdcard/mmssms.db'" 2> /dev/null; then
adb pull /sdcard/mmssms.db "$backup_dir/mmssms.db" 2> /dev/null && log " ✓ SMS/MMS backed up"
adb shell "rm /sdcard/mmssms.db" 2> /dev/null || true
else
warn " ⚠ SMS/MMS backup requires root (skipping)"
fi
# 3. Backup contacts
log "Backing up contacts..."
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/contacts2.db /sdcard/contacts2.db'" 2>/dev/null; then
adb pull /sdcard/contacts2.db "$backup_dir/contacts2.db" 2>/dev/null && log " ✓ Contacts backed up"
adb shell "rm /sdcard/contacts2.db" 2>/dev/null || true
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/contacts2.db /sdcard/contacts2.db'" 2> /dev/null; then
adb pull /sdcard/contacts2.db "$backup_dir/contacts2.db" 2> /dev/null && log " ✓ Contacts backed up"
adb shell "rm /sdcard/contacts2.db" 2> /dev/null || true
else
warn " ⚠ Contacts backup requires root (skipping)"
fi
# 4. Backup call logs
log "Backing up call logs..."
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/calllog.db /sdcard/calllog.db'" 2>/dev/null; then
adb pull /sdcard/calllog.db "$backup_dir/calllog.db" 2>/dev/null && log " ✓ Call logs backed up"
adb shell "rm /sdcard/calllog.db" 2>/dev/null || true
if adb shell "su -c 'cp /data/data/com.android.providers.contacts/databases/calllog.db /sdcard/calllog.db'" 2> /dev/null; then
adb pull /sdcard/calllog.db "$backup_dir/calllog.db" 2> /dev/null && log " ✓ Call logs backed up"
adb shell "rm /sdcard/calllog.db" 2> /dev/null || true
else
warn " ⚠ Call logs backup requires root (skipping)"
fi
# 5. Backup app list
log "Backing up installed apps list..."
adb shell "pm list packages -f" >"$backup_dir/installed_apps.txt"
adb shell "pm list packages -f" > "$backup_dir/installed_apps.txt"
log " ✓ App list saved to installed_apps.txt"
# 6. Backup APKs for user-installed apps (optional, can be large)
@ -361,10 +361,10 @@ backup_device_data() {
local apk_path
apk_path=$(adb shell "pm path $pkg" | head -n1 | sed 's/package://')
if [[ -n $apk_path ]]; then
adb pull "$apk_path" "$apk_dir/${pkg}.apk" >/dev/null 2>&1 && count=$((count + 1))
adb pull "$apk_path" "$apk_dir/${pkg}.apk" > /dev/null 2>&1 && count=$((count + 1))
fi
fi
done <<<"$user_apps"
done <<< "$user_apps"
log " ✓ Backed up $count APK files"
fi
@ -402,13 +402,13 @@ backup_device_data() {
echo
echo "Installed Apps:"
adb shell "pm list packages -3" | sed 's/package:/ - /'
} >"$backup_dir/device_info.txt"
} > "$backup_dir/device_info.txt"
log " ✓ Device info saved"
# Summary
local backup_size
backup_size=$(du -sh "$backup_dir" 2>/dev/null | cut -f1 || echo "unknown")
backup_size=$(du -sh "$backup_dir" 2> /dev/null | cut -f1 || echo "unknown")
echo
echo -e "${GREEN}╔═══════════════════════════════════════════════════════╗${NC}"
@ -445,7 +445,7 @@ check_device() {
print_header "Checking Device Connection"
log "Starting ADB server..."
adb start-server >/dev/null 2>&1 || true
adb start-server > /dev/null 2>&1 || true
log "Waiting for device..."
if ! adb wait-for-device; then
@ -474,11 +474,11 @@ check_device() {
# Check device properties
local model
model=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r\n' || echo "Unknown")
model=$(adb shell getprop ro.product.model 2> /dev/null | tr -d '\r\n' || echo "Unknown")
log "Model: $model"
local android_version
android_version=$(adb shell getprop ro.build.version.release 2>/dev/null | tr -d '\r\n' || echo "Unknown")
android_version=$(adb shell getprop ro.build.version.release 2> /dev/null | tr -d '\r\n' || echo "Unknown")
log "Android version: $android_version"
local battery_level
@ -494,7 +494,7 @@ check_device() {
# Check if bootloader is unlocked
local unlock_status
unlock_status=$(adb shell getprop ro.boot.verifiedbootstate 2>/dev/null | tr -d '\r\n' || echo "unknown")
unlock_status=$(adb shell getprop ro.boot.verifiedbootstate 2> /dev/null | tr -d '\r\n' || echo "unknown")
if [[ $unlock_status == "orange" || $unlock_status == "red" ]]; then
log "Bootloader unlock status: ${GREEN}UNLOCKED${NC}"
else
@ -503,7 +503,7 @@ check_device() {
# Check if OEM unlocking is enabled
local oem_unlock
oem_unlock=$(adb shell getprop sys.oem_unlock_allowed 2>/dev/null | tr -d '\r\n' || echo "unknown")
oem_unlock=$(adb shell getprop sys.oem_unlock_allowed 2> /dev/null | tr -d '\r\n' || echo "unknown")
if [[ $oem_unlock == "1" ]]; then
log "OEM unlocking: ${GREEN}ENABLED${NC}"
else
@ -653,7 +653,7 @@ install_mtkclient() {
cd "$mtk_dir"
python3 -m pip install --user -r requirements.txt || warn "Some dependencies may have failed to install"
python3 -m pip install --user . || warn "MTKClient installation may be incomplete"
cd - >/dev/null
cd - > /dev/null
log "MTKClient installed successfully"
return 0
@ -705,7 +705,7 @@ extract_boot_with_mtkclient() {
# Activate venv and extract boot and vbmeta
if [[ ! -d "$mtk_dir/venv" ]]; then
error "MTKClient virtual environment not found. Run: $SCRIPT_NAME install-mtk"
cd - >/dev/null
cd - > /dev/null
return 1
fi
@ -726,16 +726,16 @@ extract_boot_with_mtkclient() {
python3 mtk.py reset || warn "Failed to reset device, please reboot manually"
# Deactivate venv if function exists
type deactivate &>/dev/null && deactivate
type deactivate &> /dev/null && deactivate
cd - >/dev/null
cd - > /dev/null
return 0
else
# Deactivate venv if function exists
type deactivate &>/dev/null && deactivate
type deactivate &> /dev/null && deactivate
error "Failed to extract boot image with MTKClient"
cd - >/dev/null
cd - > /dev/null
return 1
fi
}
@ -765,11 +765,11 @@ extract_boot_image() {
# Method 2: Try to pull boot partition directly via ADB
local boot_partition
boot_partition=$(adb shell "find /dev/block -name boot | head -n1" 2>/dev/null | tr -d '\r\n' || echo "")
boot_partition=$(adb shell "find /dev/block -name boot | head -n1" 2> /dev/null | tr -d '\r\n' || echo "")
if [[ -n $boot_partition ]]; then
log "Found boot partition: $boot_partition"
if adb pull "$boot_partition" "$boot_img" 2>/dev/null; then
if adb pull "$boot_partition" "$boot_img" 2> /dev/null; then
log "Boot image extracted successfully"
BOOT_IMG="$boot_img"
return 0
@ -777,13 +777,13 @@ extract_boot_image() {
fi
# Method 3: Try to get boot partition via by-name
boot_partition=$(adb shell "ls /dev/block/by-name/boot*" 2>/dev/null | head -n1 | tr -d '\r\n' || echo "")
boot_partition=$(adb shell "ls /dev/block/by-name/boot*" 2> /dev/null | head -n1 | tr -d '\r\n' || echo "")
if [[ -n $boot_partition ]]; then
log "Found boot partition: $boot_partition"
if adb shell "su -c 'dd if=$boot_partition of=/sdcard/boot.img'" 2>/dev/null &&
adb pull /sdcard/boot.img "$boot_img" 2>/dev/null; then
adb shell rm /sdcard/boot.img 2>/dev/null || true
if adb shell "su -c 'dd if=$boot_partition of=/sdcard/boot.img'" 2> /dev/null &&
adb pull /sdcard/boot.img "$boot_img" 2> /dev/null; then
adb shell rm /sdcard/boot.img 2> /dev/null || true
log "Boot image extracted successfully"
BOOT_IMG="$boot_img"
return 0
@ -821,7 +821,7 @@ patch_boot_with_magisk() {
fi
log "Installing Magisk APK on device..."
if ! adb install -r "$magisk_apk" 2>/dev/null; then
if ! adb install -r "$magisk_apk" 2> /dev/null; then
warn "Magisk APK installation failed (may already be installed)"
fi

View File

@ -42,7 +42,7 @@ setup_systemless_hosts() {
adb shell "su -c 'mkdir -p /data/adb/modules/systemless_hosts/system/etc'" || die "Failed to create module directory"
# Create module.prop
cat >"$WORK_DIR/module.prop" <<'EOF'
cat > "$WORK_DIR/module.prop" << 'EOF'
id=systemless_hosts
name=Systemless Hosts
version=1.0
@ -95,8 +95,8 @@ enable_module() {
print_header "Enabling Systemless Hosts Module"
log "Removing disable flag if present..."
adb shell "su -c 'rm -f /data/adb/modules/systemless_hosts/disable'" 2>/dev/null || true
adb shell "su -c 'rm -f /data/adb/modules/systemless_hosts/remove'" 2>/dev/null || true
adb shell "su -c 'rm -f /data/adb/modules/systemless_hosts/disable'" 2> /dev/null || true
adb shell "su -c 'rm -f /data/adb/modules/systemless_hosts/remove'" 2> /dev/null || true
log "Module enabled"
@ -130,7 +130,7 @@ verify_hosts() {
log "Checking if hosts file is active..."
local test_domain="doubleclick.net"
local result
result=$(adb shell "su -c 'cat /system/etc/hosts | grep -c $test_domain'" 2>/dev/null || echo "0")
result=$(adb shell "su -c 'cat /system/etc/hosts | grep -c $test_domain'" 2> /dev/null || echo "0")
if [[ $result -gt 0 ]]; then
log "✓ Hosts file is active and blocking domains"

View File

@ -38,7 +38,7 @@ fi
log "Setting up media organizer startup service..."
# Create systemd service file
cat >"$SERVICE_FILE" <<EOF
cat > "$SERVICE_FILE" << EOF
[Unit]
Description=Media File Organizer
After=graphical-session.target

View File

@ -64,7 +64,7 @@ download_python_docs() {
local url="https://www.python.org/ftp/python/doc/3.12.8/python-3.12.8-docs-html.tar.bz2"
local archive="/tmp/python-docs.tar.bz2"
if curl -L -o "$archive" "$url" 2>/dev/null; then
if curl -L -o "$archive" "$url" 2> /dev/null; then
print_status "Extracting..."
tar -xjf "$archive" -C "$dest" --strip-components=1
rm -f "$archive"
@ -86,21 +86,21 @@ build_python_index() {
# Create searchable index: term -> file path
{
# Index library modules
find "$dest/library" -name "*.html" -exec basename {} .html \; 2>/dev/null | while read -r mod; do
find "$dest/library" -name "*.html" -exec basename {} .html \; 2> /dev/null | while read -r mod; do
echo "$mod $dest/library/$mod.html"
done
# Index built-in functions from functions.html
if [ -f "$dest/library/functions.html" ]; then
grep -oP '(?<=id=")[^"]+' "$dest/library/functions.html" 2>/dev/null | while read -r func; do
grep -oP '(?<=id=")[^"]+' "$dest/library/functions.html" 2> /dev/null | while read -r func; do
echo "$func $dest/library/functions.html#$func"
done
fi
# Index from general index
if [ -f "$dest/genindex.html" ]; then
grep -oP 'href="([^"]+)"[^>]*>([^<]+)' "$dest/genindex.html" 2>/dev/null | \
sed -E 's/href="([^"]+)"[^>]*>([^<]+)/\2 \1/' | \
grep -oP 'href="([^"]+)"[^>]*>([^<]+)' "$dest/genindex.html" 2> /dev/null |
sed -E 's/href="([^"]+)"[^>]*>([^<]+)/\2 \1/' |
head -5000
fi
} | sort -u > "$index"
@ -125,13 +125,13 @@ download_cpp_docs() {
mkdir -p "$dest"
# Method 1: Use cppman if available (best - fetches and caches on demand)
if command -v cppman &>/dev/null; then
if command -v cppman &> /dev/null; then
print_status "Found cppman, caching common C++ references..."
cppman -s cppreference.com 2>/dev/null
cppman -c 2>/dev/null # Cache all pages
cppman -s cppreference.com 2> /dev/null
cppman -c 2> /dev/null # Cache all pages
print_success "cppman configured - use 'cppman <term>' for lookups"
print_status "Cppman cache at: ~/.cache/cppman/"
ln -sf ~/.cache/cppman "$dest/cppman_cache" 2>/dev/null
ln -sf ~/.cache/cppman "$dest/cppman_cache" 2> /dev/null
build_cpp_index
return 0
fi
@ -146,9 +146,9 @@ download_cpp_docs() {
fi
# Method 3: Try AUR package (Arch Linux)
if command -v yay &>/dev/null; then
if command -v yay &> /dev/null; then
print_status "Installing cppreference from AUR..."
if yay -S --noconfirm cppreference 2>/dev/null; then
if yay -S --noconfirm cppreference 2> /dev/null; then
# Link to installed docs (the package uses /en not /html)
if [ -d /usr/share/doc/cppreference/en ]; then
ln -sf /usr/share/doc/cppreference "$dest/system"
@ -169,9 +169,9 @@ download_cpp_docs() {
for url in "${urls[@]}"; do
print_status "Trying: $url"
if curl -fL -o "$archive" "$url" 2>/dev/null; then
if curl -fL -o "$archive" "$url" 2> /dev/null; then
print_status "Extracting (this may take a while)..."
if tar -xJf "$archive" -C "$dest" 2>/dev/null; then
if tar -xJf "$archive" -C "$dest" 2> /dev/null; then
rm -f "$archive"
print_success "C/C++ documentation installed to $dest"
build_cpp_index
@ -197,25 +197,25 @@ build_cpp_index() {
{
# Find all HTML files and extract identifiers
# Format: term|filepath (using | as separator to handle spaces)
find "$search_dir" -name "*.html" -type f 2>/dev/null | while read -r file; do
find "$search_dir" -name "*.html" -type f 2> /dev/null | while read -r file; do
# Extract meaningful term from path (e.g., /en/cpp/container/vector.html -> vector)
local term
term=$(basename "$file" .html)
# Skip index files and overly generic names
[[ "$term" == "index" ]] && continue
[[ $term == "index" ]] && continue
echo "${term}|${file}"
done
# Also index by path components for better discoverability
# e.g., cpp/container/vector -> vector
find "$search_dir/en" -name "*.html" -type f 2>/dev/null | while read -r file; do
find "$search_dir/en" -name "*.html" -type f 2> /dev/null | while read -r file; do
# Extract path relative to en/ and create searchable term
local relpath
relpath=$(echo "$file" | sed "s|$search_dir/en/||" | sed 's|\.html$||')
# Get the last component as primary term
local term
term=$(basename "$relpath")
[[ "$term" == "index" ]] && continue
[[ $term == "index" ]] && continue
# Also add the full path as a searchable term (cpp/vector, c/stdlib/malloc)
echo "${relpath}|${file}"
done
@ -267,7 +267,7 @@ SPARSE
git checkout main
else
print_status "Updating MDN content..."
git pull --depth 1 origin main 2>/dev/null || true
git pull --depth 1 origin main 2> /dev/null || true
fi
cd - > /dev/null || exit 1
@ -284,7 +284,7 @@ SPARSE
print_success "MDN offline documentation ready"
local doc_count
doc_count=$(find "$mdn_repo/files" -name "index.md" 2>/dev/null | wc -l)
doc_count=$(find "$mdn_repo/files" -name "index.md" 2> /dev/null | wc -l)
print_status "Downloaded $doc_count documentation pages"
}
@ -301,30 +301,30 @@ build_js_index() {
# Build comprehensive index from MDN markdown files
{
# Index JavaScript reference
find "$mdn_repo/files/en-us/web/javascript/reference" -name "index.md" 2>/dev/null | while read -r file; do
find "$mdn_repo/files/en-us/web/javascript/reference" -name "index.md" 2> /dev/null | while read -r file; do
local dir
dir=$(dirname "$file")
local term
term=$(basename "$dir")
# Extract title from frontmatter if available
local title
title=$(grep -m1 "^title:" "$file" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$file" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "${term}|${file}|${title:-$term}"
done
# Index Web APIs
find "$mdn_repo/files/en-us/web/api" -name "index.md" 2>/dev/null | while read -r file; do
find "$mdn_repo/files/en-us/web/api" -name "index.md" 2> /dev/null | while read -r file; do
local dir
dir=$(dirname "$file")
local term
term=$(basename "$dir")
local title
title=$(grep -m1 "^title:" "$file" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$file" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "${term}|${file}|${title:-$term}"
done
# Index HTML elements
find "$mdn_repo/files/en-us/web/html/element" -name "index.md" 2>/dev/null | while read -r file; do
find "$mdn_repo/files/en-us/web/html/element" -name "index.md" 2> /dev/null | while read -r file; do
local dir
dir=$(dirname "$file")
local term
@ -333,24 +333,24 @@ build_js_index() {
done
# Index CSS properties
find "$mdn_repo/files/en-us/web/css" -maxdepth 2 -name "index.md" 2>/dev/null | while read -r file; do
find "$mdn_repo/files/en-us/web/css" -maxdepth 2 -name "index.md" 2> /dev/null | while read -r file; do
local dir
dir=$(dirname "$file")
local term
term=$(basename "$dir")
local title
title=$(grep -m1 "^title:" "$file" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$file" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "${term}|${file}|${title:-$term}"
done
# Index Glossary
find "$mdn_repo/files/en-us/glossary" -name "index.md" 2>/dev/null | while read -r file; do
find "$mdn_repo/files/en-us/glossary" -name "index.md" 2> /dev/null | while read -r file; do
local dir
dir=$(dirname "$file")
local term
term=$(basename "$dir")
local title
title=$(grep -m1 "^title:" "$file" 2>/dev/null | sed 's/^title:\s*//' | tr -d '"')
title=$(grep -m1 "^title:" "$file" 2> /dev/null | sed 's/^title:\s*//' | tr -d '"')
echo "${term}|${file}|${title:-$term}"
done
} | sort -t'|' -k1,1 -u > "$index"
@ -367,12 +367,12 @@ download_rust_docs() {
print_header "Rust Documentation"
local dest="$DOCS_DIR/rust"
if command -v rustup &>/dev/null; then
if command -v rustup &> /dev/null; then
print_status "Rust docs available via 'rustup doc'"
# Get the rust doc path
local rust_doc_path
rust_doc_path=$(rustup doc --path 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
rust_doc_path=$(rustup doc --path 2> /dev/null | head -1 | xargs dirname 2> /dev/null)
if [ -n "$rust_doc_path" ] && [ -d "$rust_doc_path" ]; then
ln -sf "$rust_doc_path" "$dest/std"
@ -388,12 +388,12 @@ build_rust_index() {
print_status "Building Rust documentation index..."
local index="$INDEX_DIR/rust_index.txt"
if command -v rustup &>/dev/null; then
if command -v rustup &> /dev/null; then
local rust_doc_path
rust_doc_path=$(rustup doc --path 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
rust_doc_path=$(rustup doc --path 2> /dev/null | head -1 | xargs dirname 2> /dev/null)
if [ -d "$rust_doc_path/std" ]; then
find "$rust_doc_path/std" -name "*.html" 2>/dev/null | head -2000 | while read -r file; do
find "$rust_doc_path/std" -name "*.html" 2> /dev/null | head -2000 | while read -r file; do
basename "$file" .html
done | sort -u > "$index"
fi
@ -409,12 +409,12 @@ download_go_docs() {
print_header "Go Documentation"
local dest="$DOCS_DIR/go"
if command -v go &>/dev/null; then
if command -v go &> /dev/null; then
print_status "Go docs available via 'go doc'"
# Create a reference of standard library packages
mkdir -p "$dest"
go list std 2>/dev/null > "$dest/stdlib_packages.txt"
go list std 2> /dev/null > "$dest/stdlib_packages.txt"
print_success "Go stdlib package list created"
build_go_index
@ -452,9 +452,9 @@ download_shell_docs() {
echo ""
# Get list of builtins
compgen -b 2>/dev/null | while read -r builtin; do
compgen -b 2> /dev/null | while read -r builtin; do
echo "=== $builtin ==="
help "$builtin" 2>/dev/null || echo "No help available"
help "$builtin" 2> /dev/null || echo "No help available"
echo ""
done
} > "$dest/bash_builtins.txt"
@ -566,13 +566,13 @@ build_shell_index() {
{
# Bash builtins
compgen -b 2>/dev/null | while read -r cmd; do
compgen -b 2> /dev/null | while read -r cmd; do
echo "$cmd $dest/bash_builtins.txt"
done
# Common commands from man pages
for cmd in ls cd cp mv rm mkdir cat grep sed awk find sort curl wget tar chmod; do
man_path=$(man -w "$cmd" 2>/dev/null)
man_path=$(man -w "$cmd" 2> /dev/null)
[ -n "$man_path" ] && echo "$cmd $man_path"
done
} | sort -u > "$index"
@ -586,7 +586,7 @@ build_shell_index() {
setup_zeal_docsets() {
print_header "Zeal Docsets (Optional)"
if ! command -v zeal &>/dev/null; then
if ! command -v zeal &> /dev/null; then
print_status "Zeal not installed."
print_status "Install with: pacman -S zeal (or your package manager)"
print_status "Zeal provides a GUI for offline documentation"
@ -634,8 +634,8 @@ show_status() {
for lang in python c_cpp javascript rust go shell; do
dir="$DOCS_DIR/$lang"
if [ -d "$dir" ] && [ "$(ls -A "$dir" 2>/dev/null)" ]; then
size=$(du -sh "$dir" 2>/dev/null | cut -f1)
if [ -d "$dir" ] && [ "$(ls -A "$dir" 2> /dev/null)" ]; then
size=$(du -sh "$dir" 2> /dev/null | cut -f1)
print_success "$lang: installed ($size)"
else
print_error "$lang: not installed"
@ -644,7 +644,7 @@ show_status() {
echo ""
echo "Index files:"
ls -la "$INDEX_DIR"/*.txt 2>/dev/null || echo "No indexes built yet"
ls -la "$INDEX_DIR"/*.txt 2> /dev/null || echo "No indexes built yet"
}
main() {
@ -669,10 +669,10 @@ main() {
--python)
download_python_docs
;;
--cpp|--c|--c++)
--cpp | --c | --c++)
download_cpp_docs
;;
--js|--javascript)
--js | --javascript)
download_js_docs
;;
--rust)
@ -681,7 +681,7 @@ main() {
--go)
download_go_docs
;;
--shell|--bash)
--shell | --bash)
download_shell_docs
;;
--zeal)
@ -690,7 +690,7 @@ main() {
--status)
show_status
;;
--help|-h)
--help | -h)
usage
exit 0
;;

View File

@ -25,13 +25,13 @@ KEEPASS_DIR="${1:-$HOME/Keepass}"
BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)"
# Ensure keepassxc-cli is installed
if ! command -v keepassxc-cli &>/dev/null; then
if ! command -v keepassxc-cli &> /dev/null; then
log "ERROR: 'keepassxc-cli' is not installed. Install with: sudo pacman -S keepassxc"
exit 1
fi
# Check if directory exists
if [[ ! -d "$KEEPASS_DIR" ]]; then
if [[ ! -d $KEEPASS_DIR ]]; then
log "ERROR: Directory does not exist: $KEEPASS_DIR"
exit 1
fi
@ -74,7 +74,7 @@ echo "Backups are stored in: $BACKUP_DIR"
echo "=============================================="
echo ""
read -rp "Do you want to continue? (yes/no): " CONFIRM
if [[ "$CONFIRM" != "yes" ]]; then
if [[ $CONFIRM != "yes" ]]; then
log "Aborted by user."
exit 1
fi
@ -93,7 +93,7 @@ read -rsp "Enter master password for TARGET database ($(basename "$TARGET_DB")):
echo ""
# Verify target password works
if ! echo "$TARGET_PASSWORD" | keepassxc-cli ls "$TARGET_DB" &>/dev/null; then
if ! echo "$TARGET_PASSWORD" | keepassxc-cli ls "$TARGET_DB" &> /dev/null; then
log "ERROR: Failed to open target database. Wrong password?"
exit 1
fi
@ -112,7 +112,7 @@ for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do
log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..."
# Reuse target password if user confirmed all are the same
if [[ "$SAME_PASSWORD" == "y" || "$SAME_PASSWORD" == "yes" ]]; then
if [[ $SAME_PASSWORD == "y" || $SAME_PASSWORD == "yes" ]]; then
SOURCE_PASSWORD="$TARGET_PASSWORD"
else
# Ask for source password (might be different)
@ -122,7 +122,7 @@ for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do
fi
# Verify source password
if ! echo "$SOURCE_PASSWORD" | keepassxc-cli ls "$SOURCE_DB" &>/dev/null; then
if ! echo "$SOURCE_PASSWORD" | keepassxc-cli ls "$SOURCE_DB" &> /dev/null; then
log "ERROR: Failed to open source database $(basename "$SOURCE_DB"). Wrong password?"
log "Skipping this database. You can try again later."
continue
@ -161,9 +161,9 @@ FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l)
if [[ $FINAL_COUNT -eq 1 ]]; then
log ""
FINAL_NAME="$KEEPASS_DIR/Passwords.kdbx"
if [[ "$TARGET_DB" != "$FINAL_NAME" ]]; then
if [[ $TARGET_DB != "$FINAL_NAME" ]]; then
read -rp "Rename final database to 'Passwords.kdbx'? (y/n): " RENAME_CONFIRM
if [[ "$RENAME_CONFIRM" == "y" ]]; then
if [[ $RENAME_CONFIRM == "y" ]]; then
mv -v "$TARGET_DB" "$FINAL_NAME"
log "Final database: $FINAL_NAME"
fi

View File

@ -27,7 +27,7 @@ error() {
ensure_pacman() {
require_command pacman "pacman" || error "Required command 'pacman' not found."
if ! grep -qi "arch" /etc/os-release 2>/dev/null; then
if ! grep -qi "arch" /etc/os-release 2> /dev/null; then
warn "This script was designed for Arch Linux; continuing anyway."
fi
}
@ -37,7 +37,7 @@ install_packages() {
}
print_post_install_tips() {
cat <<EOF
cat << EOF
------------------------------------------------------------------------
XFCE session installed.
@ -56,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

View File

@ -16,7 +16,7 @@ DEFAULT_RESOLUTION="320x240"
# Function to display usage
usage() {
cat <<EOF
cat << EOF
Usage: $0 <input_text_file> [resolution] [output_prefix]
Arguments:
@ -91,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}"
@ -119,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

View File

@ -16,24 +16,24 @@ MODULE_DEST="/data/adb/modules/android_guardian"
# Ensure android-tools (adb) is installed
ensure_adb_installed() {
if command -v adb &>/dev/null; then
if command -v adb &> /dev/null; then
return 0
fi
log "adb not found, installing android-tools..."
if command -v pacman &>/dev/null; then
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm android-tools || die "Failed to install android-tools"
elif command -v apt-get &>/dev/null; then
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y adb || die "Failed to install adb"
elif command -v dnf &>/dev/null; then
elif command -v dnf &> /dev/null; then
sudo dnf install -y android-tools || die "Failed to install android-tools"
else
die "adb not found and could not determine package manager. Please install android-tools manually."
fi
# Verify installation
if ! command -v adb &>/dev/null; then
if ! command -v adb &> /dev/null; then
die "adb installation failed"
fi
@ -41,7 +41,7 @@ ensure_adb_installed() {
}
show_usage() {
cat <<EOF
cat << EOF
Usage: $(basename "$0") [COMMAND]
Commands:
@ -84,30 +84,30 @@ discover_android_device() {
local found_address=""
# Ensure avahi-browse is available
if ! command -v avahi-browse &>/dev/null; then
if command -v pacman &>/dev/null; then
if ! command -v avahi-browse &> /dev/null; then
if command -v pacman &> /dev/null; then
echo "Installing avahi for device discovery..." >&2
sudo pacman -S --noconfirm avahi nss-mdns &>/dev/null || true
sudo systemctl enable --now avahi-daemon &>/dev/null || true
elif command -v apt-get &>/dev/null; then
sudo apt-get install -y avahi-utils &>/dev/null || true
sudo pacman -S --noconfirm avahi nss-mdns &> /dev/null || true
sudo systemctl enable --now avahi-daemon &> /dev/null || true
elif command -v apt-get &> /dev/null; then
sudo apt-get install -y avahi-utils &> /dev/null || true
fi
fi
if command -v avahi-browse &>/dev/null; then
if command -v avahi-browse &> /dev/null; then
echo "Scanning for Android devices (5 seconds)..." >&2
# Android wireless debugging advertises as _adb-tls-connect._tcp
local discovery_result
discovery_result=$(timeout 5 avahi-browse -rpt _adb-tls-connect._tcp 2>/dev/null | grep "^=" | head -1)
discovery_result=$(timeout 5 avahi-browse -rpt _adb-tls-connect._tcp 2> /dev/null | grep "^=" | head -1)
if [[ -n "$discovery_result" ]]; then
if [[ -n $discovery_result ]]; then
# Parse: =;eth0;IPv4;adb-...;_adb-tls-connect._tcp;local;hostname.local;192.168.x.x;port;...
local ip port
ip=$(echo "$discovery_result" | cut -d';' -f8)
port=$(echo "$discovery_result" | cut -d';' -f9)
if [[ -n "$ip" && -n "$port" ]]; then
if [[ -n $ip && -n $port ]]; then
found_address="$ip:$port"
echo "✓ Found device: $found_address" >&2
fi
@ -115,18 +115,18 @@ discover_android_device() {
fi
# Fallback: try adb's mdns discovery
if [[ -z "$found_address" ]]; then
if [[ -z $found_address ]]; then
echo "Trying adb mdns discovery..." >&2
# adb can discover devices via mdns
local mdns_result
mdns_result=$(timeout 5 adb mdns services 2>/dev/null | grep -E "adb-tls-connect|_adb\._tcp" | head -1)
mdns_result=$(timeout 5 adb mdns services 2> /dev/null | grep -E "adb-tls-connect|_adb\._tcp" | head -1)
if [[ -n "$mdns_result" ]]; then
if [[ -n $mdns_result ]]; then
# Try to extract IP:port from the result
local service_name
service_name=$(echo "$mdns_result" | awk '{print $1}')
if [[ -n "$service_name" ]]; then
if [[ -n $service_name ]]; then
# Try connecting via service name
echo "Found service: $service_name" >&2
fi
@ -154,7 +154,7 @@ cmd_pair() {
read -rp "Enter pairing IP:port (e.g., 192.168.1.100:37123): " pair_address
read -rp "Enter pairing code: " pair_code
if [[ -z "$pair_address" || -z "$pair_code" ]]; then
if [[ -z $pair_address || -z $pair_code ]]; then
die "Pairing address and code are required"
fi
@ -169,10 +169,10 @@ cmd_pair() {
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
if [[ -n "$connect_address" ]]; then
if [[ -n $connect_address ]]; then
# Save for future connections
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" >"$WIRELESS_CONFIG"
echo "$connect_address" > "$WIRELESS_CONFIG"
log "Saved connection address for future use"
# Connect now
@ -190,20 +190,20 @@ cmd_connect() {
local connect_address=""
# Check for saved address
if [[ -f "$WIRELESS_CONFIG" ]]; then
if [[ -f $WIRELESS_CONFIG ]]; then
connect_address=$(cat "$WIRELESS_CONFIG")
log "Using saved address: $connect_address"
fi
# Try auto-discovery if no saved address
if [[ -z "$connect_address" ]]; then
if [[ -z $connect_address ]]; then
echo ""
log "Searching for Android devices on network..."
connect_address=$(discover_android_device)
fi
# Manual fallback
if [[ -z "$connect_address" ]]; then
if [[ -z $connect_address ]]; then
echo ""
echo "Auto-discovery failed. Enter address manually."
echo "On phone: Settings > Developer Options > Wireless debugging"
@ -211,14 +211,14 @@ cmd_connect() {
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
if [[ -z "$connect_address" ]]; then
if [[ -z $connect_address ]]; then
die "Connection address is required"
fi
fi
# Save for future
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" >"$WIRELESS_CONFIG"
echo "$connect_address" > "$WIRELESS_CONFIG"
log "Connecting to $connect_address..."
if adb connect "$connect_address" | grep -q "connected"; then
@ -268,9 +268,9 @@ ensure_device_ready() {
echo ""
# Check if we have a saved wireless config
if [[ -f "$WIRELESS_CONFIG" ]]; then
if [[ -f $WIRELESS_CONFIG ]]; then
read -rp "Try connecting to saved wireless device? [Y/n]: " try_wireless
if [[ "${try_wireless,,}" != "n" ]]; then
if [[ ${try_wireless,,} != "n" ]]; then
cmd_connect
else
exit 1
@ -313,7 +313,7 @@ build_module() {
fi
# Append custom blocking entries
cat >>"$hosts_file" <<'CUSTOM_EOF'
cat >> "$hosts_file" << 'CUSTOM_EOF'
# ============================================
# Custom blocking entries - Android Guardian
@ -387,7 +387,7 @@ CUSTOM_EOF
echo "[BUILD] Hosts file contains $total_entries blocked domains" >&2
# Create zip
(cd "$tmp_dir" && zip -r "$module_zip" . -x "*.DS_Store") >/dev/null
(cd "$tmp_dir" && zip -r "$module_zip" . -x "*.DS_Store") > /dev/null
echo "$module_zip"
}
@ -450,9 +450,9 @@ uninstall_blocked_apps() {
blocked_apps=$(grep -v '^#' "$GUARDIAN_MODULE_DIR/blocked_apps.txt" | grep -v '^$' || true)
for package in $blocked_apps; do
if adb shell "pm list packages" 2>/dev/null | grep -q "package:$package"; then
if adb shell "pm list packages" 2> /dev/null | grep -q "package:$package"; then
log "Uninstalling blocked app: $package"
adb shell "pm uninstall $package" 2>/dev/null || true
adb shell "pm uninstall $package" 2> /dev/null || true
fi
done
}
@ -466,7 +466,7 @@ cmd_status() {
echo ""
# Check if module is installed
if adb shell "su -c 'test -d $MODULE_DEST'" 2>/dev/null; then
if adb shell "su -c 'test -d $MODULE_DEST'" 2> /dev/null; then
echo "Module: INSTALLED"
else
echo "Module: NOT INSTALLED"
@ -480,7 +480,7 @@ cmd_status() {
# Check if module is "disabled" in Magisk UI (should be auto-fixed by watchdog)
local magisk_disabled
if adb shell "su -c 'test -f $MODULE_DEST/disable'" 2>/dev/null; then
if adb shell "su -c 'test -f $MODULE_DEST/disable'" 2> /dev/null; then
magisk_disabled="YES (watchdog should fix this)"
else
magisk_disabled="No"
@ -490,7 +490,7 @@ cmd_status() {
# Check if watchdog is running
local watchdog_running
watchdog_running=$(adb shell "su -c 'pgrep -f watchdog.sh 2>/dev/null | wc -l'" | tr -d '\r')
if [ "$watchdog_running" -gt 0 ] 2>/dev/null; then
if [ "$watchdog_running" -gt 0 ] 2> /dev/null; then
echo "Watchdog: RUNNING ($watchdog_running processes)"
else
echo "Watchdog: NOT RUNNING (reboot phone to start)"
@ -548,7 +548,7 @@ cmd_uninstall() {
local status
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo ENABLED'" | tr -d '\r')
if [[ "$status" != "DISABLED" ]]; then
if [[ $status != "DISABLED" ]]; then
echo ""
echo "⚠️ Guardian must be disabled before uninstalling!"
echo " Run: $0 disable"
@ -579,7 +579,7 @@ cmd_logs() {
cmd_block_app() {
local package="${1:-}"
if [[ -z "$package" ]]; then
if [[ -z $package ]]; then
echo "Usage: $0 block-app <package.name>"
echo "Example: $0 block-app com.ubercab.eats"
exit 1
@ -591,12 +591,12 @@ cmd_block_app() {
adb shell "su -c 'echo \"$package\" >> $GUARDIAN_DATA_DIR/blocked_apps.txt'"
# Also add to local file
echo "$package" >>"$GUARDIAN_MODULE_DIR/blocked_apps.txt"
echo "$package" >> "$GUARDIAN_MODULE_DIR/blocked_apps.txt"
# Try to uninstall if currently installed
if adb shell "pm list packages" 2>/dev/null | grep -q "package:$package"; then
if adb shell "pm list packages" 2> /dev/null | grep -q "package:$package"; then
log "Uninstalling $package..."
adb shell "pm uninstall $package" 2>/dev/null || true
adb shell "pm uninstall $package" 2> /dev/null || true
fi
echo "$package added to block list"
@ -606,7 +606,7 @@ cmd_block_app() {
cmd_unblock_app() {
local package="${1:-}"
if [[ -z "$package" ]]; then
if [[ -z $package ]]; then
echo "Usage: $0 unblock-app <package.name>"
exit 1
fi
@ -617,7 +617,7 @@ cmd_unblock_app() {
adb shell "su -c 'grep -v \"^$package\$\" $GUARDIAN_DATA_DIR/blocked_apps.txt > $GUARDIAN_DATA_DIR/blocked_apps.tmp && mv $GUARDIAN_DATA_DIR/blocked_apps.tmp $GUARDIAN_DATA_DIR/blocked_apps.txt'"
# Also remove from local file
grep -v "^$package$" "$GUARDIAN_MODULE_DIR/blocked_apps.txt" >"$GUARDIAN_MODULE_DIR/blocked_apps.tmp" && mv "$GUARDIAN_MODULE_DIR/blocked_apps.tmp" "$GUARDIAN_MODULE_DIR/blocked_apps.txt"
grep -v "^$package$" "$GUARDIAN_MODULE_DIR/blocked_apps.txt" > "$GUARDIAN_MODULE_DIR/blocked_apps.tmp" && mv "$GUARDIAN_MODULE_DIR/blocked_apps.tmp" "$GUARDIAN_MODULE_DIR/blocked_apps.txt"
echo "$package removed from block list"
}
@ -638,46 +638,46 @@ COMMAND="${1:-install}"
shift || true
case "$COMMAND" in
install)
install)
cmd_install
;;
status)
status)
cmd_status
;;
disable)
disable)
cmd_disable
;;
enable)
enable)
cmd_enable
;;
uninstall)
uninstall)
cmd_uninstall
;;
logs)
logs)
cmd_logs
;;
block-app)
block-app)
cmd_block_app "$@"
;;
unblock-app)
unblock-app)
cmd_unblock_app "$@"
;;
list-blocked)
list-blocked)
cmd_list_blocked
;;
pair)
pair)
cmd_pair
;;
connect)
connect)
cmd_connect
;;
disconnect)
disconnect)
cmd_disconnect
;;
-h | --help | help)
-h | --help | help)
show_usage
;;
*)
*)
echo "Unknown command: $COMMAND"
show_usage
exit 1