mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 14:23:08 +02:00
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:
parent
64b3116a5c
commit
2888c0b53d
@ -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}"
|
||||
|
||||
|
||||
@ -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}"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
================================
|
||||
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
============================================
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
;;
|
||||
*)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
#==============================================================================
|
||||
|
||||
@ -15,7 +15,7 @@ DEFAULT_RESOLUTION="320x240"
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
cat <<EOF
|
||||
cat << EOF
|
||||
Usage: $0 <input_image> [resolution] [output_image]
|
||||
|
||||
Arguments:
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -17,7 +17,7 @@ OUTPUT_FORMAT="jpg"
|
||||
PDF_FILES=()
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
cat << EOF
|
||||
Usage:
|
||||
$(basename "$0") [OPTIONS] PDF_FILE [PDF_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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
;;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user