mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 11:43:03 +02:00
Fix shell script formatting and add PR workflow validation (#3)
* Initial plan * fix: format shell scripts with shfmt (convert tabs to 2 spaces) Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> * feat: enhance shell-check workflow for PR pre-merge validation - Add pull_request_target trigger to check PRs from forks - Add explicit failure message with instructions - Create BRANCH_PROTECTION.md with setup guide - Ensure workflow runs on all PRs targeting main/master Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> * refactor: improve workflow security and remove redundant exit code - Remove pull_request_target to avoid executing untrusted fork code - Remove redundant exit 1 from failure step - Update documentation to reflect changes - Standard pull_request trigger handles forks securely Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kuhyx <147418882+kuhyx@users.noreply.github.com>
This commit is contained in:
parent
c72ddb6ddb
commit
18b9f020bb
71
.github/BRANCH_PROTECTION.md
vendored
Normal file
71
.github/BRANCH_PROTECTION.md
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Branch Protection and Pre-Merge Checks
|
||||||
|
|
||||||
|
This repository uses GitHub Actions to ensure code quality before merging to `main` or `master` branches.
|
||||||
|
|
||||||
|
## Required Checks
|
||||||
|
|
||||||
|
### Shell Script Linting
|
||||||
|
|
||||||
|
The `Shell Script Linting` workflow automatically runs on:
|
||||||
|
- Pull requests targeting `main` or `master` branches (including from forks)
|
||||||
|
- Direct pushes to `main` or `master` branches
|
||||||
|
|
||||||
|
This workflow checks:
|
||||||
|
- Shell script syntax with `shellcheck`
|
||||||
|
- Code formatting with `shfmt` (2-space indentation, no tabs)
|
||||||
|
- Optional checks: `checkbashisms`, syntax validation
|
||||||
|
|
||||||
|
## Enabling Branch Protection
|
||||||
|
|
||||||
|
To make the shell linting check **required** before merging PRs, follow these steps:
|
||||||
|
|
||||||
|
1. Go to repository **Settings** → **Branches**
|
||||||
|
2. Click **Add rule** or edit existing rule for `main`/`master`
|
||||||
|
3. Configure the following settings:
|
||||||
|
- ✅ **Require a pull request before merging**
|
||||||
|
- ✅ **Require status checks to pass before merging**
|
||||||
|
- Search for and select: `Lint Shell Scripts`
|
||||||
|
- ✅ **Require branches to be up to date before merging** (recommended)
|
||||||
|
- ✅ **Do not allow bypassing the above settings** (recommended)
|
||||||
|
4. Click **Create** or **Save changes**
|
||||||
|
|
||||||
|
## Running Checks Locally
|
||||||
|
|
||||||
|
Before pushing changes, run the linting script locally to catch issues early:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/meta/shell_check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Install required linters on Arch Linux (if needed)
|
||||||
|
- Check all shell scripts in the repository
|
||||||
|
- Report any formatting or syntax issues
|
||||||
|
|
||||||
|
To auto-fix formatting issues:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install shfmt if not already installed
|
||||||
|
# On Arch: sudo pacman -S shfmt
|
||||||
|
# Or download from: https://github.com/mvdan/sh/releases
|
||||||
|
|
||||||
|
# Fix formatting in-place
|
||||||
|
find . -name "*.sh" -type f | xargs shfmt -w -i 2 -ci -sr -s
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Gets Checked
|
||||||
|
|
||||||
|
The workflow validates shell scripts with these extensions or shebangs:
|
||||||
|
- `*.sh`, `*.bash`, `*.zsh` files
|
||||||
|
- Executable files with shell shebangs (`#!/bin/bash`, `#!/bin/sh`, etc.)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the check fails on your PR:
|
||||||
|
|
||||||
|
1. Review the workflow logs to see which files failed
|
||||||
|
2. Run `bash scripts/meta/shell_check.sh` locally to reproduce
|
||||||
|
3. Fix the issues (usually formatting with `shfmt -w -i 2 -ci -sr -s`)
|
||||||
|
4. Commit and push the fixes
|
||||||
|
|
||||||
|
The workflow will automatically re-run on new commits to the PR.
|
||||||
7
.github/workflows/shell-check.yml
vendored
7
.github/workflows/shell-check.yml
vendored
@ -48,3 +48,10 @@ jobs:
|
|||||||
- name: Report status
|
- name: Report status
|
||||||
if: success()
|
if: success()
|
||||||
run: echo "✅ All shell scripts passed linting checks!"
|
run: echo "✅ All shell scripts passed linting checks!"
|
||||||
|
|
||||||
|
- name: Provide help on failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "❌ Shell script linting failed!"
|
||||||
|
echo "This check is required to merge PRs into main/master."
|
||||||
|
echo "Please run 'bash scripts/meta/shell_check.sh' locally and fix any issues."
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
[ "${GPU_VENDOR}" = "amd" ] || {
|
[ "${GPU_VENDOR}" = "amd" ] || {
|
||||||
echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}"
|
echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
AMD_INSTALL_XF86=${AMD_INSTALL_XF86:-0}
|
AMD_INSTALL_XF86=${AMD_INSTALL_XF86:-0}
|
||||||
@ -32,9 +32,9 @@ warn() { echo "[amd][warn] $*" >&2; }
|
|||||||
|
|
||||||
# Detect multilib enabled
|
# Detect multilib enabled
|
||||||
if grep -q '^\[multilib\]' /etc/pacman.conf; then
|
if grep -q '^\[multilib\]' /etc/pacman.conf; then
|
||||||
MULTILIB_ENABLED=1
|
MULTILIB_ENABLED=1
|
||||||
else
|
else
|
||||||
MULTILIB_ENABLED=0
|
MULTILIB_ENABLED=0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Basic packages
|
# Basic packages
|
||||||
@ -58,49 +58,49 @@ LIB32_AMDVLK_PKG="lib32-amdvlk"
|
|||||||
|
|
||||||
# Simple AUR builder (reused from NVIDIA script style)
|
# Simple AUR builder (reused from NVIDIA script style)
|
||||||
_build_aur_pkg() {
|
_build_aur_pkg() {
|
||||||
local pkg="$1"
|
local pkg="$1"
|
||||||
local url="https://aur.archlinux.org/${pkg}.git"
|
local url="https://aur.archlinux.org/${pkg}.git"
|
||||||
mkdir -p "$HOME/aur"
|
mkdir -p "$HOME/aur"
|
||||||
cd "$HOME/aur"
|
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
|
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"
|
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
|
yes | makepkg -s -c -C --noconfirm --needed
|
||||||
local built=(*.pkg.tar.zst)
|
local built=(*.pkg.tar.zst)
|
||||||
yes | sudo pacman -U --noconfirm "${built[@]}"
|
yes | sudo pacman -U --noconfirm "${built[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
_install_repo_or_aur() {
|
_install_repo_or_aur() {
|
||||||
local pkg="$1"
|
local pkg="$1"
|
||||||
if pacman -Si "$pkg" >/dev/null 2>&1; then
|
if pacman -Si "$pkg" > /dev/null 2>&1; then
|
||||||
if pacman -Qi "$pkg" >/dev/null 2>&1; then
|
if pacman -Qi "$pkg" > /dev/null 2>&1; then
|
||||||
vlog "$pkg already installed"
|
vlog "$pkg already installed"
|
||||||
else
|
else
|
||||||
yes | sudo pacman -Sy --noconfirm "$pkg"
|
yes | sudo pacman -Sy --noconfirm "$pkg"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
info "Building AUR package: $pkg"
|
info "Building AUR package: $pkg"
|
||||||
_build_aur_pkg "$pkg"
|
_build_aur_pkg "$pkg"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
info "Installing AMD GPU stack"
|
info "Installing AMD GPU stack"
|
||||||
for p in "${BASE_PKGS[@]}" "$VULKAN_PKG"; do _install_repo_or_aur "$p"; done
|
for p in "${BASE_PKGS[@]}" "$VULKAN_PKG"; do _install_repo_or_aur "$p"; done
|
||||||
|
|
||||||
if [ "$AMD_INSTALL_XF86" = 1 ]; then
|
if [ "$AMD_INSTALL_XF86" = 1 ]; then
|
||||||
_install_repo_or_aur "$XF86_PKG"
|
_install_repo_or_aur "$XF86_PKG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# AMDVLK optional (install after vulkan-radeon if requested)
|
# AMDVLK optional (install after vulkan-radeon if requested)
|
||||||
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then
|
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then
|
||||||
_install_repo_or_aur "$AMDVLK_PKG"
|
_install_repo_or_aur "$AMDVLK_PKG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $MULTILIB_ENABLED = 1 ] || [ "$AMD_INSTALL_LIB32" = 1 ]; then
|
if [ $MULTILIB_ENABLED = 1 ] || [ "$AMD_INSTALL_LIB32" = 1 ]; then
|
||||||
for p in "${LIB32_BASE[@]}" "$LIB32_VULKAN_PKG"; do _install_repo_or_aur "$p"; done
|
for p in "${LIB32_BASE[@]}" "$LIB32_VULKAN_PKG"; do _install_repo_or_aur "$p"; done
|
||||||
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then _install_repo_or_aur "$LIB32_AMDVLK_PKG"; fi
|
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then _install_repo_or_aur "$LIB32_AMDVLK_PKG"; fi
|
||||||
else
|
else
|
||||||
vlog "Skipping 32-bit packages (multilib disabled)"
|
vlog "Skipping 32-bit packages (multilib disabled)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect SI / CIK codename presence for optional amdgpu enablement
|
# Detect SI / CIK codename presence for optional amdgpu enablement
|
||||||
@ -113,41 +113,41 @@ for n in "${SI_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_SI=1 && bre
|
|||||||
for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done
|
for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done
|
||||||
|
|
||||||
if [ "$AMD_ENABLE_SI_CIK" = "1" ] || { [ "$AMD_ENABLE_SI_CIK" = "auto" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; }; then
|
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)"
|
info "Configuring amdgpu for SI/CIK (IS_SI=$IS_SI IS_CIK=$IS_CIK)"
|
||||||
TMP_CONF=$(mktemp)
|
TMP_CONF=$(mktemp)
|
||||||
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\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"
|
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >> "$TMP_CONF"
|
||||||
sudo mkdir -p /etc/modprobe.d
|
sudo mkdir -p /etc/modprobe.d
|
||||||
sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf
|
sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf
|
||||||
rm -f "$TMP_CONF"
|
rm -f "$TMP_CONF"
|
||||||
# Ensure amdgpu early in MODULES
|
# Ensure amdgpu early in MODULES
|
||||||
if [ -f /etc/mkinitcpio.conf ]; then
|
if [ -f /etc/mkinitcpio.conf ]; then
|
||||||
if ! grep -q '^MODULES=.*amdgpu' /etc/mkinitcpio.conf; then
|
if ! grep -q '^MODULES=.*amdgpu' /etc/mkinitcpio.conf; then
|
||||||
sudo sed -i 's/^MODULES=\(.*\)/MODULES=(amdgpu radeon)/' /etc/mkinitcpio.conf || true
|
sudo sed -i 's/^MODULES=\(.*\)/MODULES=(amdgpu radeon)/' /etc/mkinitcpio.conf || true
|
||||||
fi
|
fi
|
||||||
if ! grep -q 'modconf' /etc/mkinitcpio.conf; then
|
if ! grep -q 'modconf' /etc/mkinitcpio.conf; then
|
||||||
warn "modconf hook not found in mkinitcpio.conf (needed for module options)"
|
warn "modconf hook not found in mkinitcpio.conf (needed for module options)"
|
||||||
fi
|
fi
|
||||||
if [ "$AMD_SKIP_INITRAMFS" != 1 ]; then
|
if [ "$AMD_SKIP_INITRAMFS" != 1 ]; then
|
||||||
info "Regenerating initramfs (mkinitcpio -P)"
|
info "Regenerating initramfs (mkinitcpio -P)"
|
||||||
sudo mkinitcpio -P || warn "mkinitcpio failed; review manually"
|
sudo mkinitcpio -P || warn "mkinitcpio failed; review manually"
|
||||||
else
|
else
|
||||||
info "Skipping initramfs regeneration per AMD_SKIP_INITRAMFS=1"
|
info "Skipping initramfs regeneration per AMD_SKIP_INITRAMFS=1"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
warn "/etc/mkinitcpio.conf not found; skipping MODULES update"
|
warn "/etc/mkinitcpio.conf not found; skipping MODULES update"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
vlog "SI/CIK enablement not required (AMD_ENABLE_SI_CIK=$AMD_ENABLE_SI_CIK IS_SI=$IS_SI IS_CIK=$IS_CIK)"
|
vlog "SI/CIK enablement not required (AMD_ENABLE_SI_CIK=$AMD_ENABLE_SI_CIK IS_SI=$IS_SI IS_CIK=$IS_CIK)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check active kernel driver
|
# 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}')
|
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}')
|
||||||
info "Kernel driver in use: ${KDRV:-unknown}"
|
info "Kernel driver in use: ${KDRV:-unknown}"
|
||||||
|
|
||||||
if [ "$KDRV" = "radeon" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; then
|
if [ "$KDRV" = "radeon" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; then
|
||||||
warn "radeon driver still active for SI/CIK; reboot may be required to switch to amdgpu"
|
warn "radeon driver still active for SI/CIK; reboot may be required to switch to amdgpu"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export AMD_STACK_DONE=1
|
export AMD_STACK_DONE=1
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
[ "$GPU_VENDOR" = "intel" ] || {
|
[ "$GPU_VENDOR" = "intel" ] || {
|
||||||
echo "Intel installer invoked but GPU_VENDOR=$GPU_VENDOR"
|
echo "Intel installer invoked but GPU_VENDOR=$GPU_VENDOR"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
INTEL_USE_AMBER=${INTEL_USE_AMBER:-0}
|
INTEL_USE_AMBER=${INTEL_USE_AMBER:-0}
|
||||||
@ -35,24 +35,24 @@ if grep -q '^\[multilib\]' /etc/pacman.conf; then MULTILIB=1; else MULTILIB=0; f
|
|||||||
|
|
||||||
# Base mesa package
|
# Base mesa package
|
||||||
if [ "$INTEL_USE_AMBER" = 1 ]; then
|
if [ "$INTEL_USE_AMBER" = 1 ]; then
|
||||||
BASE_MESA=mesa-amber
|
BASE_MESA=mesa-amber
|
||||||
LIB32_BASE=lib32-mesa-amber
|
LIB32_BASE=lib32-mesa-amber
|
||||||
else
|
else
|
||||||
BASE_MESA=mesa
|
BASE_MESA=mesa
|
||||||
LIB32_BASE=lib32-mesa
|
LIB32_BASE=lib32-mesa
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_pkg() {
|
install_pkg() {
|
||||||
local pkg="$1"
|
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"
|
vlog "$pkg already installed"
|
||||||
else
|
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"
|
yes | sudo pacman -Sy --noconfirm "$pkg"
|
||||||
else
|
else
|
||||||
warn "Package $pkg not found in repos (not handling AUR here)"
|
warn "Package $pkg not found in repos (not handling AUR here)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
info "Installing Intel GPU stack"
|
info "Installing Intel GPU stack"
|
||||||
@ -60,47 +60,47 @@ install_pkg "$BASE_MESA"
|
|||||||
|
|
||||||
# 32-bit mesa
|
# 32-bit mesa
|
||||||
if { [ "$INTEL_INSTALL_LIB32" = auto ] && [ $MULTILIB = 1 ]; } || [ "$INTEL_INSTALL_LIB32" = 1 ]; then
|
if { [ "$INTEL_INSTALL_LIB32" = auto ] && [ $MULTILIB = 1 ]; } || [ "$INTEL_INSTALL_LIB32" = 1 ]; then
|
||||||
install_pkg "$LIB32_BASE"
|
install_pkg "$LIB32_BASE"
|
||||||
else
|
else
|
||||||
vlog "Skipping 32-bit mesa (INTEL_INSTALL_LIB32=$INTEL_INSTALL_LIB32 MULTILIB=$MULTILIB)"
|
vlog "Skipping 32-bit mesa (INTEL_INSTALL_LIB32=$INTEL_INSTALL_LIB32 MULTILIB=$MULTILIB)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Vulkan
|
# Vulkan
|
||||||
if [ "$INTEL_INSTALL_VULKAN" = 1 ]; then
|
if [ "$INTEL_INSTALL_VULKAN" = 1 ]; then
|
||||||
install_pkg vulkan-intel
|
install_pkg vulkan-intel
|
||||||
if { [ "$INTEL_INSTALL_LIB32_VK" = auto ] && [ $MULTILIB = 1 ]; } || [ "$INTEL_INSTALL_LIB32_VK" = 1 ]; then
|
if { [ "$INTEL_INSTALL_LIB32_VK" = auto ] && [ $MULTILIB = 1 ]; } || [ "$INTEL_INSTALL_LIB32_VK" = 1 ]; then
|
||||||
install_pkg lib32-vulkan-intel
|
install_pkg lib32-vulkan-intel
|
||||||
else
|
else
|
||||||
vlog "Skipping 32-bit vulkan (INTEL_INSTALL_LIB32_VK=$INTEL_INSTALL_LIB32_VK MULTILIB=$MULTILIB)"
|
vlog "Skipping 32-bit vulkan (INTEL_INSTALL_LIB32_VK=$INTEL_INSTALL_LIB32_VK MULTILIB=$MULTILIB)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Legacy xf86-video-intel (not recommended)
|
# Legacy xf86-video-intel (not recommended)
|
||||||
if [ "$INTEL_INSTALL_XF86" = 1 ]; then
|
if [ "$INTEL_INSTALL_XF86" = 1 ]; then
|
||||||
install_pkg xf86-video-intel
|
install_pkg xf86-video-intel
|
||||||
else
|
else
|
||||||
vlog "Not installing xf86-video-intel (INTEL_INSTALL_XF86=$INTEL_INSTALL_XF86)"
|
vlog "Not installing xf86-video-intel (INTEL_INSTALL_XF86=$INTEL_INSTALL_XF86)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# GuC / HuC enablement
|
# GuC / HuC enablement
|
||||||
if [ -n "$INTEL_ENABLE_GUC" ]; then
|
if [ -n "$INTEL_ENABLE_GUC" ]; then
|
||||||
if ! echo "$INTEL_ENABLE_GUC" | grep -Eq '^[0-3]$'; then
|
if ! echo "$INTEL_ENABLE_GUC" | grep -Eq '^[0-3]$'; then
|
||||||
warn "INTEL_ENABLE_GUC must be 0..3; ignoring"
|
warn "INTEL_ENABLE_GUC must be 0..3; ignoring"
|
||||||
else
|
else
|
||||||
info "Configuring enable_guc=$INTEL_ENABLE_GUC"
|
info "Configuring enable_guc=$INTEL_ENABLE_GUC"
|
||||||
sudo mkdir -p /etc/modprobe.d
|
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
|
if [ "$INTEL_SKIP_INITRAMFS" != 1 ] && [ -f /etc/mkinitcpio.conf ]; then
|
||||||
info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change"
|
info "Regenerating initramfs (mkinitcpio -P) for GuC/HuC change"
|
||||||
sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually"
|
sudo mkinitcpio -P || warn "mkinitcpio failed; continue manually"
|
||||||
else
|
else
|
||||||
info "Skipping initramfs regeneration (INTEL_SKIP_INITRAMFS=$INTEL_SKIP_INITRAMFS)"
|
info "Skipping initramfs regeneration (INTEL_SKIP_INITRAMFS=$INTEL_SKIP_INITRAMFS)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Report kernel driver
|
# 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}')
|
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'i915|xe' | head -n1 | awk '{print $1}')
|
||||||
info "Kernel driver in use: ${KDRV:-unknown}"
|
info "Kernel driver in use: ${KDRV:-unknown}"
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,11 @@ set -e
|
|||||||
|
|
||||||
# Function to play a sound on error
|
# Function to play a sound on error
|
||||||
play_error_sound() {
|
play_error_sound() {
|
||||||
#pactl set-sink-volume @DEFAULT_SINK@ +50%
|
#pactl set-sink-volume @DEFAULT_SINK@ +50%
|
||||||
for _ in 1 2 3; do
|
for _ in 1 2 3; do
|
||||||
paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga
|
paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga
|
||||||
done
|
done
|
||||||
#pactl set-sink-volume @DEFAULT_SINK@ -50%
|
#pactl set-sink-volume @DEFAULT_SINK@ -50%
|
||||||
}
|
}
|
||||||
|
|
||||||
# Trap errors and call the play_error_sound function
|
# Trap errors and call the play_error_sound function
|
||||||
@ -20,108 +20,108 @@ git config --global init.defaultBranch main
|
|||||||
|
|
||||||
# GPU detection (now split vendor-specific logic)
|
# GPU detection (now split vendor-specific logic)
|
||||||
if [ -f "./detect_gpu.sh" ]; then
|
if [ -f "./detect_gpu.sh" ]; then
|
||||||
# shellcheck source=./detect_gpu.sh disable=SC1091
|
# shellcheck source=./detect_gpu.sh disable=SC1091
|
||||||
. ./detect_gpu.sh
|
. ./detect_gpu.sh
|
||||||
elif [ -f "./detect_gpu_and_install.sh" ]; then
|
elif [ -f "./detect_gpu_and_install.sh" ]; then
|
||||||
# shellcheck source=./detect_gpu_and_install.sh disable=SC1091
|
# shellcheck source=./detect_gpu_and_install.sh disable=SC1091
|
||||||
. ./detect_gpu_and_install.sh
|
. ./detect_gpu_and_install.sh
|
||||||
else
|
else
|
||||||
echo "GPU detection scripts not found; continuing without GPU specific installation."
|
echo "GPU detection scripts not found; continuing without GPU specific installation."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_from_aur() {
|
install_from_aur() {
|
||||||
local repo_url pkg_name repo_dir
|
local repo_url pkg_name repo_dir
|
||||||
repo_url="$1"
|
repo_url="$1"
|
||||||
pkg_name="$2"
|
pkg_name="$2"
|
||||||
|
|
||||||
mkdir -p "$HOME/aur"
|
mkdir -p "$HOME/aur"
|
||||||
cd "$HOME/aur" || return 1
|
cd "$HOME/aur" || return 1
|
||||||
repo_dir="$(basename "$repo_url" .git)"
|
repo_dir="$(basename "$repo_url" .git)"
|
||||||
|
|
||||||
if [ ! -d "$repo_dir" ]; then
|
if [ ! -d "$repo_dir" ]; then
|
||||||
git clone "$repo_url"
|
git clone "$repo_url"
|
||||||
else
|
else
|
||||||
echo "Repository $repo_dir already cloned; updating"
|
echo "Repository $repo_dir already cloned; updating"
|
||||||
(cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true)
|
(cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true)
|
||||||
fi
|
fi
|
||||||
cd "$repo_dir" || return 1
|
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"
|
echo "$pkg_name is already installed"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Cleaning old package artifacts to avoid duplicate -U targets"
|
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)"
|
echo "Building $pkg_name (clean build)"
|
||||||
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
|
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
|
||||||
if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then
|
if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then
|
||||||
echo "Build failed for $pkg_name" >&2
|
echo "Build failed for $pkg_name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect only the freshly built packages (should now be only current version)
|
# Collect only the freshly built packages (should now be only current version)
|
||||||
mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n')
|
mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n')
|
||||||
if [ ${#built_pkgs[@]} -eq 0 ]; then
|
if [ ${#built_pkgs[@]} -eq 0 ]; then
|
||||||
echo "No package files produced for $pkg_name" >&2
|
echo "No package files produced for $pkg_name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Installing built package(s): ${built_pkgs[*]}"
|
echo "Installing built package(s): ${built_pkgs[*]}"
|
||||||
if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then
|
if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then
|
||||||
echo "Installation failed for $pkg_name" >&2
|
echo "Installation failed for $pkg_name" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper: try to install from AUR and log result to done.txt/failed.txt
|
# Helper: try to install from AUR and log result to done.txt/failed.txt
|
||||||
try_aur_install() {
|
try_aur_install() {
|
||||||
local repo_url="$1"
|
local repo_url="$1"
|
||||||
local pkg_name="$2"
|
local pkg_name="$2"
|
||||||
if install_from_aur "$repo_url" "$pkg_name"; then
|
if install_from_aur "$repo_url" "$pkg_name"; then
|
||||||
echo "$pkg_name" >>done.txt
|
echo "$pkg_name" >> done.txt
|
||||||
else
|
else
|
||||||
echo "$pkg_name" >>failed.txt
|
echo "$pkg_name" >> failed.txt
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
process_packages() {
|
process_packages() {
|
||||||
local file_path
|
local file_path
|
||||||
file_path="$1"
|
file_path="$1"
|
||||||
: >failed.txt
|
: > failed.txt
|
||||||
: >done.txt
|
: > done.txt
|
||||||
|
|
||||||
while IFS= read -r pkg_name; do
|
while IFS= read -r pkg_name; do
|
||||||
if [ -z "$pkg_name" ]; then
|
if [ -z "$pkg_name" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local repo_url repo_dir
|
local repo_url repo_dir
|
||||||
repo_url="https://aur.archlinux.org/${pkg_name}-git.git"
|
repo_url="https://aur.archlinux.org/${pkg_name}-git.git"
|
||||||
repo_dir="${pkg_name}-git"
|
repo_dir="${pkg_name}-git"
|
||||||
|
|
||||||
git clone "$repo_url"
|
git clone "$repo_url"
|
||||||
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
|
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
|
||||||
echo "Repository $repo_dir is empty, trying without -git suffix"
|
echo "Repository $repo_dir is empty, trying without -git suffix"
|
||||||
repo_url="https://aur.archlinux.org/${pkg_name}.git"
|
repo_url="https://aur.archlinux.org/${pkg_name}.git"
|
||||||
repo_dir="${pkg_name}"
|
repo_dir="${pkg_name}"
|
||||||
|
|
||||||
git clone "$repo_url"
|
git clone "$repo_url"
|
||||||
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
|
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
|
||||||
echo "Repository $repo_dir is empty, trying to install with pacman"
|
echo "Repository $repo_dir is empty, trying to install with pacman"
|
||||||
if sudo pacman -Sy --noconfirm "$pkg_name"; then
|
if sudo pacman -Sy --noconfirm "$pkg_name"; then
|
||||||
echo "$pkg_name" >>done.txt
|
echo "$pkg_name" >> done.txt
|
||||||
else
|
else
|
||||||
echo "$pkg_name" >>failed.txt
|
echo "$pkg_name" >> failed.txt
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
try_aur_install "$repo_url" "$pkg_name"
|
try_aur_install "$repo_url" "$pkg_name"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
try_aur_install "$repo_url" "$pkg_name"
|
try_aur_install "$repo_url" "$pkg_name"
|
||||||
fi
|
fi
|
||||||
done <"$file_path"
|
done < "$file_path"
|
||||||
}
|
}
|
||||||
|
|
||||||
sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak
|
sudo cp /etc/makepkg.conf /etc/makepkg.conf.bak
|
||||||
@ -132,161 +132,161 @@ sudo cp ./pacman.conf /etc/pacman.conf
|
|||||||
# sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf
|
# sudo cp ./mkinitcpio.conf /etc/mkinitcpio.conf
|
||||||
# mkinitcpio -P
|
# mkinitcpio -P
|
||||||
# Reflector install / service management (idempotent & resilient)
|
# 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"
|
echo "reflector already installed"
|
||||||
else
|
else
|
||||||
yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)"
|
yes | sudo pacman -Sy --noconfirm reflector || echo "Warning: reflector install failed (continuing)"
|
||||||
fi
|
fi
|
||||||
# Prefer timer over service (Arch default)
|
# Prefer timer over service (Arch default)
|
||||||
if systemctl list-unit-files | grep -q '^reflector.timer'; then
|
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"
|
echo "reflector.timer already enabled"
|
||||||
else
|
else
|
||||||
sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer"
|
sudo systemctl enable reflector.timer || echo "Warning: could not enable reflector.timer"
|
||||||
fi
|
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"
|
echo "reflector.timer already active"
|
||||||
else
|
else
|
||||||
if ! sudo systemctl start reflector.timer; then
|
if ! sudo systemctl start reflector.timer; then
|
||||||
echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)"
|
echo "Warning: failed to start reflector.timer (check: systemctl status reflector.timer; journalctl -xeu reflector.timer)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
elif systemctl list-unit-files | grep -q '^reflector.service'; then
|
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"
|
echo "reflector.service already enabled"
|
||||||
else
|
else
|
||||||
sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service"
|
sudo systemctl enable reflector.service || echo "Warning: could not enable reflector.service"
|
||||||
fi
|
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"
|
echo "reflector.service already running"
|
||||||
else
|
else
|
||||||
if ! sudo systemctl start reflector.service; then
|
if ! sudo systemctl start reflector.service; then
|
||||||
echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)"
|
echo "Warning: failed to start reflector.service (check: systemctl status reflector.service; journalctl -xeu reflector.service)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "reflector systemd unit not found (neither timer nor service)"
|
echo "reflector systemd unit not found (neither timer nor service)"
|
||||||
fi
|
fi
|
||||||
# Read AUR packages from file (needed before pacman processing)
|
# Read AUR packages from file (needed before pacman processing)
|
||||||
declare -a aur_packages=()
|
declare -a aur_packages=()
|
||||||
declare -a aur_package_names=()
|
declare -a aur_package_names=()
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
|
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
|
||||||
aur_packages+=("$line")
|
aur_packages+=("$line")
|
||||||
aur_package_names+=("${line%% *}")
|
aur_package_names+=("${line%% *}")
|
||||||
fi
|
fi
|
||||||
done <"aur_packages.txt"
|
done < "aur_packages.txt"
|
||||||
|
|
||||||
# Helper: Check if all subpackages are installed
|
# Helper: Check if all subpackages are installed
|
||||||
# Returns 0 if ALL subpackages are installed, 1 otherwise
|
# Returns 0 if ALL subpackages are installed, 1 otherwise
|
||||||
all_subpackages_installed() {
|
all_subpackages_installed() {
|
||||||
local -n sub_pkgs_ref=$1
|
local -n sub_pkgs_ref=$1
|
||||||
for subpkg in "${sub_pkgs_ref[@]}"; do
|
for subpkg in "${sub_pkgs_ref[@]}"; do
|
||||||
if ! pacman -Qi "$subpkg" &>/dev/null; then
|
if ! pacman -Qi "$subpkg" &> /dev/null; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read pacman packages from file
|
# Read pacman packages from file
|
||||||
declare -a pacman_packages
|
declare -a pacman_packages
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
# Skip empty lines and comments (lines not starting with alphanumeric characters)
|
# Skip empty lines and comments (lines not starting with alphanumeric characters)
|
||||||
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
|
if [[ -n $line && $line =~ ^[a-z0-9] ]]; then
|
||||||
pacman_packages+=("$line")
|
pacman_packages+=("$line")
|
||||||
fi
|
fi
|
||||||
done <"pacman_packages.txt"
|
done < "pacman_packages.txt"
|
||||||
|
|
||||||
for pkg in "${pacman_packages[@]}"; do
|
for pkg in "${pacman_packages[@]}"; do
|
||||||
# Skip NVIDIA packages if GPU is not NVIDIA
|
# Skip NVIDIA packages if GPU is not NVIDIA
|
||||||
if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then
|
if [ "$GPU_VENDOR" != "nvidia" ] && { [ "$pkg" = "nvidia" ] || [ "$pkg" = "nvidia-utils" ] || [ "$pkg" = "lib32-nvidia-utils" ]; }; then
|
||||||
echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)"
|
echo "Skipping $pkg (GPU vendor: $GPU_VENDOR)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
# Check for texlive subpackages
|
# Check for texlive subpackages
|
||||||
if [ "$pkg" == "texlive" ]; then
|
if [ "$pkg" == "texlive" ]; then
|
||||||
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
||||||
texlive_sub_pkgs=(
|
texlive_sub_pkgs=(
|
||||||
texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra
|
texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra
|
||||||
texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities
|
texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities
|
||||||
texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience
|
texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience
|
||||||
texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks
|
texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks
|
||||||
texlive-publishers texlive-xetex
|
texlive-publishers texlive-xetex
|
||||||
)
|
)
|
||||||
if all_subpackages_installed texlive_sub_pkgs; then
|
if all_subpackages_installed texlive_sub_pkgs; then
|
||||||
echo "All texlive subpackages are installed, skipping texlive"
|
echo "All texlive subpackages are installed, skipping texlive"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for texlive-lang subpackages
|
# Check for texlive-lang subpackages
|
||||||
if [ "$pkg" == "texlive-lang" ]; then
|
if [ "$pkg" == "texlive-lang" ]; then
|
||||||
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
||||||
texlive_lang_sub_pkgs=(
|
texlive_lang_sub_pkgs=(
|
||||||
texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic
|
texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic
|
||||||
texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench
|
texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench
|
||||||
texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese
|
texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese
|
||||||
texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese
|
texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese
|
||||||
texlive-langspanish
|
texlive-langspanish
|
||||||
)
|
)
|
||||||
if all_subpackages_installed texlive_lang_sub_pkgs; then
|
if all_subpackages_installed texlive_lang_sub_pkgs; then
|
||||||
echo "All texlive-lang subpackages are installed, skipping texlive-lang"
|
echo "All texlive-lang subpackages are installed, skipping texlive-lang"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! pacman -Qi "$pkg" &>/dev/null; then
|
if ! pacman -Qi "$pkg" &> /dev/null; then
|
||||||
if ! printf '%s
|
if ! printf '%s
|
||||||
' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then
|
' "${aur_package_names[@]}" | grep -Fxq "$pkg"; then
|
||||||
yes | sudo pacman -Sy --noconfirm "$pkg"
|
yes | sudo pacman -Sy --noconfirm "$pkg"
|
||||||
else
|
else
|
||||||
echo "$pkg exists in AUR packages, skipping pacman installation"
|
echo "$pkg exists in AUR packages, skipping pacman installation"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "$pkg is already installed"
|
echo "$pkg is already installed"
|
||||||
fi
|
fi
|
||||||
done
|
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
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
||||||
else
|
else
|
||||||
echo "nvm is already installed"
|
echo "nvm is already installed"
|
||||||
fi
|
fi
|
||||||
export NVM_DIR="$HOME/.nvm"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
. "$NVM_DIR/nvm.sh"
|
. "$NVM_DIR/nvm.sh"
|
||||||
else
|
else
|
||||||
echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2
|
echo "nvm.sh not found at $NVM_DIR/nvm.sh" >&2
|
||||||
fi
|
fi
|
||||||
if command -v nvm &>/dev/null; then
|
if command -v nvm &> /dev/null; then
|
||||||
nvm i v18.20.5
|
nvm i v18.20.5
|
||||||
nvm install --lts
|
nvm install --lts
|
||||||
else
|
else
|
||||||
echo "nvm command unavailable; skipping Node installation" >&2
|
echo "nvm command unavailable; skipping Node installation" >&2
|
||||||
fi
|
fi
|
||||||
sudo systemctl enable bluetooth.service
|
sudo systemctl enable bluetooth.service
|
||||||
sudo systemctl start bluetooth.service
|
sudo systemctl start bluetooth.service
|
||||||
|
|
||||||
for entry in "${aur_packages[@]}"; do
|
for entry in "${aur_packages[@]}"; do
|
||||||
pkg_name=${entry%% *}
|
pkg_name=${entry%% *}
|
||||||
repo_url=${entry#* }
|
repo_url=${entry#* }
|
||||||
if [ "$repo_url" = "$pkg_name" ] || [ -z "$repo_url" ]; then
|
if [ "$repo_url" = "$pkg_name" ] || [ -z "$repo_url" ]; then
|
||||||
repo_url="https://aur.archlinux.org/${pkg_name}.git"
|
repo_url="https://aur.archlinux.org/${pkg_name}.git"
|
||||||
fi
|
fi
|
||||||
install_from_aur "$repo_url" "$pkg_name"
|
install_from_aur "$repo_url" "$pkg_name"
|
||||||
done
|
done
|
||||||
|
|
||||||
cd ~/linux-configuration/fresh-install
|
cd ~/linux-configuration/fresh-install
|
||||||
if [ ! -d "$HOME/.config/mpv" ]; then
|
if [ ! -d "$HOME/.config/mpv" ]; then
|
||||||
mkdir -p "$HOME/.config/mpv"
|
mkdir -p "$HOME/.config/mpv"
|
||||||
fi
|
fi
|
||||||
cp mpv.conf "$HOME/.config/mpv/mpv.conf"
|
cp mpv.conf "$HOME/.config/mpv/mpv.conf"
|
||||||
|
|
||||||
if [ ! -d "$HOME/.oh-my-zsh" ]; then
|
if [ ! -d "$HOME/.oh-my-zsh" ]; then
|
||||||
yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
yes | sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
|
||||||
else
|
else
|
||||||
echo "Oh My Zsh is already installed"
|
echo "Oh My Zsh is already installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd ~/linux-configuration
|
cd ~/linux-configuration
|
||||||
|
|||||||
@ -7,85 +7,85 @@ LOGTAG=hosts-guard-hook
|
|||||||
|
|
||||||
# Check if target has a read-only mount
|
# Check if target has a read-only mount
|
||||||
is_ro_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
|
# Count mount layers for the target
|
||||||
mount_layers_count() {
|
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 all bind mount layers
|
||||||
collapse_mounts() {
|
collapse_mounts() {
|
||||||
local i=0
|
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
|
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=$((i + 1))
|
||||||
((i > 20)) && break
|
((i > 20)) && break
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
local cnt
|
local cnt
|
||||||
cnt=$(mount_layers_count)
|
cnt=$(mount_layers_count)
|
||||||
while ((cnt > 1)); do
|
while ((cnt > 1)); do
|
||||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
umount -l "$TARGET" > /dev/null 2>&1 || break
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
((i > 20)) && break
|
((i > 20)) && break
|
||||||
cnt=$(mount_layers_count)
|
cnt=$(mount_layers_count)
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Stop systemd units related to hosts guard
|
# Stop systemd units related to hosts guard
|
||||||
stop_units_if_present() {
|
stop_units_if_present() {
|
||||||
local units=(hosts-bind-mount.service hosts-guard.path)
|
local units=(hosts-bind-mount.service hosts-guard.path)
|
||||||
for u in "${units[@]}"; do
|
for u in "${units[@]}"; do
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl > /dev/null 2>&1; then
|
||||||
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
|
if systemctl list-unit-files 2> /dev/null | grep -q "^$u"; then
|
||||||
systemctl stop "$u" >/dev/null 2>&1 || true
|
systemctl stop "$u" > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove immutable/append-only attributes
|
# Remove immutable/append-only attributes
|
||||||
remove_host_attrs() {
|
remove_host_attrs() {
|
||||||
if command -v lsattr >/dev/null 2>&1; then
|
if command -v lsattr > /dev/null 2>&1; then
|
||||||
local attrs
|
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
|
if echo "$attrs" | grep -q " i "; then
|
||||||
chattr -i "$TARGET" >/dev/null 2>&1 || true
|
chattr -i "$TARGET" > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
if echo "$attrs" | grep -q " a "; then
|
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
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply immutable attribute
|
# Apply immutable attribute
|
||||||
apply_immutable() {
|
apply_immutable() {
|
||||||
if command -v chattr >/dev/null 2>&1; then
|
if command -v chattr > /dev/null 2>&1; then
|
||||||
chattr +i "$TARGET" >/dev/null 2>&1 || true
|
chattr +i "$TARGET" > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply a single read-only bind mount layer
|
# Apply a single read-only bind mount layer
|
||||||
apply_ro_bind_mount() {
|
apply_ro_bind_mount() {
|
||||||
mount --bind "$TARGET" "$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
|
mount -o remount,ro,bind "$TARGET" > /dev/null 2>&1 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start the path watcher service
|
# Start the path watcher service
|
||||||
start_path_watcher() {
|
start_path_watcher() {
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
if command -v systemctl > /dev/null 2>&1; then
|
||||||
systemctl start hosts-guard.path >/dev/null 2>&1 || true
|
systemctl start hosts-guard.path > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log to system logger and run log file
|
# Log to system logger and run log file
|
||||||
log_hook() {
|
log_hook() {
|
||||||
local phase="$1"
|
local phase="$1"
|
||||||
local state="$2"
|
local state="$2"
|
||||||
logger -t "$LOGTAG" "$phase: $state"
|
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
|
# Run enforcement script if available
|
||||||
if [[ -x $ENFORCE ]]; then
|
if [[ -x $ENFORCE ]]; then
|
||||||
"$ENFORCE" >/dev/null 2>&1 || true
|
"$ENFORCE" > /dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Apply protections
|
# Apply protections
|
||||||
|
|||||||
@ -20,7 +20,7 @@ collapse_mounts
|
|||||||
|
|
||||||
# Ensure writable by remounting if still read-only
|
# Ensure writable by remounting if still read-only
|
||||||
if is_ro_mount; then
|
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
|
fi
|
||||||
|
|
||||||
log_hook "pre" "unlocking(done)"
|
log_hook "pre" "unlocking(done)"
|
||||||
|
|||||||
318
hosts/install.sh
318
hosts/install.sh
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Re-run with sudo if not root
|
# Re-run with sudo if not root
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
exec sudo -E bash "$0" "$@"
|
exec sudo -E bash "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
@ -11,18 +11,18 @@ FLUSH_DNS=0
|
|||||||
|
|
||||||
# Parse CLI flags
|
# Parse CLI flags
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--flush-dns)
|
--flush-dns)
|
||||||
FLUSH_DNS=1
|
FLUSH_DNS=1
|
||||||
;;
|
;;
|
||||||
--no-flush-dns)
|
--no-flush-dns)
|
||||||
FLUSH_DNS=0
|
FLUSH_DNS=0
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
echo "Usage: $0 [--flush-dns|--no-flush-dns]"
|
echo "Usage: $0 [--flush-dns|--no-flush-dns]"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -39,136 +39,136 @@ CUSTOM_ENTRIES_STATE_FILE="/etc/hosts.custom-entries.state"
|
|||||||
# Extract custom blocked entries from a hosts file or heredoc section
|
# Extract custom blocked entries from a hosts file or heredoc section
|
||||||
# Returns only the "0.0.0.0 domain.com" lines (normalized, sorted, unique)
|
# Returns only the "0.0.0.0 domain.com" lines (normalized, sorted, unique)
|
||||||
extract_custom_entries_from_script() {
|
extract_custom_entries_from_script() {
|
||||||
# Extract entries from the heredoc in this script (between EOF markers after "Custom blocking entries")
|
# Extract entries from the heredoc in this script (between EOF markers after "Custom blocking entries")
|
||||||
local script_path="$1"
|
local script_path="$1"
|
||||||
sed -n '/^# Custom blocking entries$/,/^EOF$/p' "$script_path" |
|
sed -n '/^# Custom blocking entries$/,/^EOF$/p' "$script_path" |
|
||||||
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
||||||
awk '{print $2}' |
|
awk '{print $2}' |
|
||||||
sort -u
|
sort -u
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker)
|
# Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker)
|
||||||
extract_custom_entries_from_hosts() {
|
extract_custom_entries_from_hosts() {
|
||||||
local hosts_file="$1"
|
local hosts_file="$1"
|
||||||
if [[ ! -f $hosts_file ]]; then
|
if [[ ! -f $hosts_file ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" |
|
sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" |
|
||||||
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
||||||
awk '{print $2}' |
|
awk '{print $2}' |
|
||||||
sort -u
|
sort -u
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load previously saved custom entries state
|
# Load previously saved custom entries state
|
||||||
load_saved_custom_entries() {
|
load_saved_custom_entries() {
|
||||||
if [[ -f $CUSTOM_ENTRIES_STATE_FILE ]]; then
|
if [[ -f $CUSTOM_ENTRIES_STATE_FILE ]]; then
|
||||||
sort -u "$CUSTOM_ENTRIES_STATE_FILE"
|
sort -u "$CUSTOM_ENTRIES_STATE_FILE"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save current custom entries to state file
|
# Save current custom entries to state file
|
||||||
save_custom_entries_state() {
|
save_custom_entries_state() {
|
||||||
local entries="$1"
|
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"
|
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
|
# Helper function to count non-empty lines
|
||||||
count_lines() {
|
count_lines() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
if [[ -z $input ]]; then
|
if [[ -z $input ]]; then
|
||||||
echo 0
|
echo 0
|
||||||
else
|
else
|
||||||
echo "$input" | grep -c . 2>/dev/null || echo 0
|
echo "$input" | grep -c . 2> /dev/null || echo 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main protection check
|
# Main protection check
|
||||||
check_custom_entries_protection() {
|
check_custom_entries_protection() {
|
||||||
local script_path
|
local script_path
|
||||||
script_path="$(readlink -f "$0")"
|
script_path="$(readlink -f "$0")"
|
||||||
|
|
||||||
# Get new entries from the script's heredoc
|
# Get new entries from the script's heredoc
|
||||||
local new_entries
|
local new_entries
|
||||||
new_entries=$(extract_custom_entries_from_script "$script_path")
|
new_entries=$(extract_custom_entries_from_script "$script_path")
|
||||||
local new_count
|
local new_count
|
||||||
new_count=$(count_lines "$new_entries")
|
new_count=$(count_lines "$new_entries")
|
||||||
|
|
||||||
# Get saved/existing entries (prefer state file, fall back to current /etc/hosts)
|
# Get saved/existing entries (prefer state file, fall back to current /etc/hosts)
|
||||||
local saved_entries
|
local saved_entries
|
||||||
saved_entries=$(load_saved_custom_entries)
|
saved_entries=$(load_saved_custom_entries)
|
||||||
if [[ -z $saved_entries ]]; then
|
if [[ -z $saved_entries ]]; then
|
||||||
# First run or state file missing - extract from current /etc/hosts if it has our marker
|
# First run or state file missing - extract from current /etc/hosts if it has our marker
|
||||||
saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts")
|
saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts")
|
||||||
fi
|
fi
|
||||||
local saved_count
|
local saved_count
|
||||||
saved_count=$(count_lines "$saved_entries")
|
saved_count=$(count_lines "$saved_entries")
|
||||||
|
|
||||||
# If no saved state exists, this is first installation - allow it
|
# If no saved state exists, this is first installation - allow it
|
||||||
if [[ $saved_count -eq 0 ]]; then
|
if [[ $saved_count -eq 0 ]]; then
|
||||||
echo "ℹ️ First installation detected - no protection check needed."
|
echo "ℹ️ First installation detected - no protection check needed."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find entries that were removed
|
# Find entries that were removed
|
||||||
local removed_entries
|
local removed_entries
|
||||||
removed_entries=$(comm -23 <(echo "$saved_entries") <(echo "$new_entries"))
|
removed_entries=$(comm -23 <(echo "$saved_entries") <(echo "$new_entries"))
|
||||||
local removed_count
|
local removed_count
|
||||||
removed_count=$(count_lines "$removed_entries")
|
removed_count=$(count_lines "$removed_entries")
|
||||||
|
|
||||||
# Find entries that are new
|
# Find entries that are new
|
||||||
local added_entries
|
local added_entries
|
||||||
added_entries=$(comm -13 <(echo "$saved_entries") <(echo "$new_entries"))
|
added_entries=$(comm -13 <(echo "$saved_entries") <(echo "$new_entries"))
|
||||||
local added_count
|
local added_count
|
||||||
added_count=$(count_lines "$added_entries")
|
added_count=$(count_lines "$added_entries")
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "📊 Custom Entries Protection Check:"
|
echo "📊 Custom Entries Protection Check:"
|
||||||
echo " Previously blocked: $saved_count entries"
|
echo " Previously blocked: $saved_count entries"
|
||||||
echo " Currently in script: $new_count entries"
|
echo " Currently in script: $new_count entries"
|
||||||
echo " Removed: $removed_count | Added: $added_count"
|
echo " Removed: $removed_count | Added: $added_count"
|
||||||
|
|
||||||
# RULE 1: No entries removed - always OK
|
# RULE 1: No entries removed - always OK
|
||||||
if [[ $removed_count -eq 0 ]]; then
|
if [[ $removed_count -eq 0 ]]; then
|
||||||
echo " ✅ No entries removed - protection check passed."
|
echo " ✅ No entries removed - protection check passed."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# RULE 2: Entries were removed - BLOCK INSTALLATION
|
# RULE 2: Entries were removed - BLOCK INSTALLATION
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================================"
|
echo "============================================================"
|
||||||
echo " ❌ INSTALLATION BLOCKED - CUSTOM ENTRIES REMOVED"
|
echo " ❌ INSTALLATION BLOCKED - CUSTOM ENTRIES REMOVED"
|
||||||
echo "============================================================"
|
echo "============================================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "You are attempting to REMOVE the following blocked entries:"
|
echo "You are attempting to REMOVE the following blocked entries:"
|
||||||
while IFS= read -r entry; do
|
while IFS= read -r entry; do
|
||||||
echo " - $entry"
|
echo " - $entry"
|
||||||
done <<<"$removed_entries"
|
done <<< "$removed_entries"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This is NOT allowed. The only way to unblock sites is to:"
|
echo "This is NOT allowed. The only way to unblock sites is to:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 1. Manually edit /etc/hosts (requires removing chattr protection)"
|
echo " 1. Manually edit /etc/hosts (requires removing chattr protection)"
|
||||||
echo " 2. Delete the state file /etc/hosts.custom-entries.state"
|
echo " 2. Delete the state file /etc/hosts.custom-entries.state"
|
||||||
echo " (also protected with chattr)"
|
echo " (also protected with chattr)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "These manual steps are intentionally difficult to prevent"
|
echo "These manual steps are intentionally difficult to prevent"
|
||||||
echo "impulsive unblocking. If you really need to unblock something,"
|
echo "impulsive unblocking. If you really need to unblock something,"
|
||||||
echo "you'll have to work for it."
|
echo "you'll have to work for it."
|
||||||
echo ""
|
echo ""
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run the protection check
|
# Run the protection check
|
||||||
if ! check_custom_entries_protection; then
|
if ! check_custom_entries_protection; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Enable systemd-resolved
|
# Enable systemd-resolved
|
||||||
sudo systemctl enable systemd-resolved
|
sudo systemctl enable systemd-resolved
|
||||||
|
|
||||||
# Remove all attributes from /etc/hosts to allow modifications
|
# 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
|
# Source and local cache configuration
|
||||||
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
|
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
|
||||||
@ -177,33 +177,33 @@ LOCAL_CACHE="/etc/hosts.stevenblack"
|
|||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
extract_date_epoch_from_file() {
|
extract_date_epoch_from_file() {
|
||||||
# Grep "# Date:" line and convert to epoch seconds (UTC)
|
# Grep "# Date:" line and convert to epoch seconds (UTC)
|
||||||
local f="$1"
|
local f="$1"
|
||||||
local line
|
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
|
if [[ -n $line ]]; then
|
||||||
date -u -d "$line" +%s 2>/dev/null || echo ""
|
date -u -d "$line" +%s 2> /dev/null || echo ""
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_remote_header() {
|
fetch_remote_header() {
|
||||||
# Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head
|
# Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head
|
||||||
local out="$1"
|
local out="$1"
|
||||||
if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then
|
if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
# Fallback – may download more, but we only keep first lines
|
# 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
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
download_remote_full_to() {
|
download_remote_full_to() {
|
||||||
local out="$1"
|
local out="$1"
|
||||||
curl -LfsS "$URL" -o "$out"
|
curl -LfsS "$URL" -o "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Decide whether to use cache or update
|
# Decide whether to use cache or update
|
||||||
@ -212,47 +212,47 @@ trap 'rm -f "$TMP_REMOTE_HEAD"' EXIT
|
|||||||
|
|
||||||
REMOTE_AVAILABLE=0
|
REMOTE_AVAILABLE=0
|
||||||
if fetch_remote_header "$TMP_REMOTE_HEAD"; then
|
if fetch_remote_header "$TMP_REMOTE_HEAD"; then
|
||||||
REMOTE_AVAILABLE=1
|
REMOTE_AVAILABLE=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NEED_UPDATE=0
|
NEED_UPDATE=0
|
||||||
|
|
||||||
if [[ -f $LOCAL_CACHE ]]; then
|
if [[ -f $LOCAL_CACHE ]]; then
|
||||||
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
|
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
|
||||||
else
|
else
|
||||||
local_epoch=""
|
local_epoch=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $REMOTE_AVAILABLE -eq 1 ]]; then
|
if [[ $REMOTE_AVAILABLE -eq 1 ]]; then
|
||||||
remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD")
|
remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD")
|
||||||
if [[ -n $local_epoch && -n $remote_epoch && $local_epoch -ge $remote_epoch ]]; then
|
if [[ -n $local_epoch && -n $remote_epoch && $local_epoch -ge $remote_epoch ]]; then
|
||||||
echo "Using cached StevenBlack hosts (up-to-date)."
|
echo "Using cached StevenBlack hosts (up-to-date)."
|
||||||
else
|
else
|
||||||
echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..."
|
echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..."
|
||||||
NEED_UPDATE=1
|
NEED_UPDATE=1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -f $LOCAL_CACHE ]]; then
|
if [[ -f $LOCAL_CACHE ]]; then
|
||||||
echo "No internet; using cached StevenBlack hosts."
|
echo "No internet; using cached StevenBlack hosts."
|
||||||
else
|
else
|
||||||
echo "Error: No internet and no cached StevenBlack hosts found." >&2
|
echo "Error: No internet and no cached StevenBlack hosts found." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we have a fresh cache if needed
|
# Ensure we have a fresh cache if needed
|
||||||
if [[ $NEED_UPDATE -eq 1 ]]; then
|
if [[ $NEED_UPDATE -eq 1 ]]; then
|
||||||
TMP_DL=$(mktemp)
|
TMP_DL=$(mktemp)
|
||||||
if download_remote_full_to "$TMP_DL"; then
|
if download_remote_full_to "$TMP_DL"; then
|
||||||
# Save raw upstream to cache
|
# Save raw upstream to cache
|
||||||
sudo mv "$TMP_DL" "$LOCAL_CACHE"
|
sudo mv "$TMP_DL" "$LOCAL_CACHE"
|
||||||
sudo chmod 644 "$LOCAL_CACHE"
|
sudo chmod 644 "$LOCAL_CACHE"
|
||||||
echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE"
|
echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE"
|
||||||
else
|
else
|
||||||
rm -f "$TMP_DL"
|
rm -f "$TMP_DL"
|
||||||
echo "Error: Failed to download latest StevenBlack hosts." >&2
|
echo "Error: Failed to download latest StevenBlack hosts." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install the base hosts from cache into /etc/hosts
|
# Install the base hosts from cache into /etc/hosts
|
||||||
@ -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
|
# Add custom entries for YouTube and Discord
|
||||||
echo "Adding 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
|
# Custom blocking entries
|
||||||
# YouTube
|
# YouTube
|
||||||
@ -407,17 +407,17 @@ echo "Saving custom entries state for protection mechanism..."
|
|||||||
script_path="$(readlink -f "$0")"
|
script_path="$(readlink -f "$0")"
|
||||||
current_custom_entries=$(extract_custom_entries_from_script "$script_path")
|
current_custom_entries=$(extract_custom_entries_from_script "$script_path")
|
||||||
# Remove immutable from state file if it exists
|
# 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"
|
save_custom_entries_state "$current_custom_entries"
|
||||||
echo "✅ Custom entries state saved to $CUSTOM_ENTRIES_STATE_FILE"
|
echo "✅ Custom entries state saved to $CUSTOM_ENTRIES_STATE_FILE"
|
||||||
|
|
||||||
# Optionally flush DNS caches
|
# Optionally flush DNS caches
|
||||||
if [[ $FLUSH_DNS -eq 1 ]]; then
|
if [[ $FLUSH_DNS -eq 1 ]]; then
|
||||||
echo "Flushing DNS caches..."
|
echo "Flushing DNS caches..."
|
||||||
sudo systemd-resolve --flush-caches
|
sudo systemd-resolve --flush-caches
|
||||||
sudo systemctl restart NetworkManager.service
|
sudo systemctl restart NetworkManager.service
|
||||||
else
|
else
|
||||||
echo "DNS cache flush skipped (use --flush-dns to enable)."
|
echo "DNS cache flush skipped (use --flush-dns to enable)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -7,32 +7,32 @@ SHUTDOWN_CONFIG="/etc/shutdown-schedule.conf"
|
|||||||
|
|
||||||
# Function to show error state in i3blocks and exit
|
# Function to show error state in i3blocks and exit
|
||||||
show_error() {
|
show_error() {
|
||||||
local message="$1"
|
local message="$1"
|
||||||
echo "⏻ $message"
|
echo "⏻ $message"
|
||||||
echo "⏻"
|
echo "⏻"
|
||||||
echo "#FF79C6" # Pink/magenta for config errors
|
echo "#FF79C6" # Pink/magenta for config errors
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate and load config file
|
# Validate and load config file
|
||||||
if [[ ! -f "$SHUTDOWN_CONFIG" ]]; then
|
if [[ ! -f $SHUTDOWN_CONFIG ]]; then
|
||||||
show_error "NO CONFIG"
|
show_error "NO CONFIG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Source the config file to get MON_WED_HOUR and THU_SUN_HOUR
|
# Source the config file to get MON_WED_HOUR and THU_SUN_HOUR
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
if ! source "$SHUTDOWN_CONFIG" 2>/dev/null; then
|
if ! source "$SHUTDOWN_CONFIG" 2> /dev/null; then
|
||||||
show_error "BAD CONFIG"
|
show_error "BAD CONFIG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate that required variables are set
|
# 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"
|
show_error "MISSING VARS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate that values are numbers
|
# 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"
|
show_error "INVALID HOURS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get current time info
|
# Get current time info
|
||||||
@ -43,22 +43,22 @@ day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
|||||||
|
|
||||||
# Determine shutdown hour based on day of week
|
# Determine shutdown hour based on day of week
|
||||||
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
||||||
# Monday-Wednesday
|
# Monday-Wednesday
|
||||||
shutdown_hour=$MON_WED_HOUR
|
shutdown_hour=$MON_WED_HOUR
|
||||||
else
|
else
|
||||||
# Thursday-Sunday
|
# Thursday-Sunday
|
||||||
shutdown_hour=$THU_SUN_HOUR
|
shutdown_hour=$THU_SUN_HOUR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
shutdown_time_minutes=$((shutdown_hour * 60))
|
shutdown_time_minutes=$((shutdown_hour * 60))
|
||||||
|
|
||||||
# Check if we're currently in the shutdown window (after shutdown time or before 05:00)
|
# Check if we're currently in the shutdown window (after shutdown time or before 05:00)
|
||||||
if [[ $current_time_minutes -ge $shutdown_time_minutes ]] || [[ $current_time_minutes -le 300 ]]; then
|
if [[ $current_time_minutes -ge $shutdown_time_minutes ]] || [[ $current_time_minutes -le 300 ]]; then
|
||||||
# We're in shutdown window - show warning
|
# We're in shutdown window - show warning
|
||||||
echo "⏻ SHUTDOWN"
|
echo "⏻ SHUTDOWN"
|
||||||
echo "⏻"
|
echo "⏻"
|
||||||
echo "#FF5555"
|
echo "#FF5555"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Calculate minutes until shutdown
|
# Calculate minutes until shutdown
|
||||||
@ -70,28 +70,28 @@ minutes=$((minutes_until_shutdown % 60))
|
|||||||
|
|
||||||
# Format output
|
# Format output
|
||||||
if [[ $hours -gt 0 ]]; then
|
if [[ $hours -gt 0 ]]; then
|
||||||
time_str="${hours}h ${minutes}m"
|
time_str="${hours}h ${minutes}m"
|
||||||
else
|
else
|
||||||
time_str="${minutes}m"
|
time_str="${minutes}m"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Color based on time remaining
|
# Color based on time remaining
|
||||||
if [[ $minutes_until_shutdown -le 30 ]]; then
|
if [[ $minutes_until_shutdown -le 30 ]]; then
|
||||||
# Less than 30 min - red warning
|
# Less than 30 min - red warning
|
||||||
color="#FF5555"
|
color="#FF5555"
|
||||||
icon="⏻"
|
icon="⏻"
|
||||||
elif [[ $minutes_until_shutdown -le 60 ]]; then
|
elif [[ $minutes_until_shutdown -le 60 ]]; then
|
||||||
# Less than 1 hour - orange warning
|
# Less than 1 hour - orange warning
|
||||||
color="#FFB86C"
|
color="#FFB86C"
|
||||||
icon="⏻"
|
icon="⏻"
|
||||||
elif [[ $minutes_until_shutdown -le 120 ]]; then
|
elif [[ $minutes_until_shutdown -le 120 ]]; then
|
||||||
# Less than 2 hours - yellow
|
# Less than 2 hours - yellow
|
||||||
color="#F1FA8C"
|
color="#F1FA8C"
|
||||||
icon="⏻"
|
icon="⏻"
|
||||||
else
|
else
|
||||||
# More than 2 hours - normal
|
# More than 2 hours - normal
|
||||||
color="#6272A4"
|
color="#6272A4"
|
||||||
icon="⏻"
|
icon="⏻"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Output for i3blocks (full_text, short_text, color)
|
# Output for i3blocks (full_text, short_text, color)
|
||||||
|
|||||||
@ -48,24 +48,24 @@ err() { printf "${RED}[✗]${NC} %s\n" "$*"; }
|
|||||||
header() { printf "\n${CYAN}=== %s ===${NC}\n" "$*"; }
|
header() { printf "\n${CYAN}=== %s ===${NC}\n" "$*"; }
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
if [[ $DRY_RUN -eq 1 ]]; then
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
echo -e "${YELLOW}DRY-RUN:${NC} $*"
|
echo -e "${YELLOW}DRY-RUN:${NC} $*"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
"$@"
|
"$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
require_root() {
|
require_root() {
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script requires root privileges."
|
echo "This script requires root privileges."
|
||||||
echo "Re-executing with sudo..."
|
echo "Re-executing with sudo..."
|
||||||
exec sudo -E bash "$0" "$@"
|
exec sudo -E bash "$0" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat << 'EOF'
|
||||||
Check and Enable Digital Wellbeing Services
|
Check and Enable Digital Wellbeing Services
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
@ -90,25 +90,25 @@ EOF
|
|||||||
######################################################################
|
######################################################################
|
||||||
ORIGINAL_ARGS=("$@")
|
ORIGINAL_ARGS=("$@")
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--dry-run)
|
--dry-run)
|
||||||
DRY_RUN=1
|
DRY_RUN=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--status)
|
--status)
|
||||||
STATUS_ONLY=1
|
STATUS_ONLY=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
err "Unknown option: $1"
|
err "Unknown option: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
require_root "${ORIGINAL_ARGS[@]}"
|
require_root "${ORIGINAL_ARGS[@]}"
|
||||||
@ -125,41 +125,41 @@ FIXES_APPLIED=0
|
|||||||
# Usage: report_and_fix issues_array status_var status_key fix_note setup_script verify_service [args...]
|
# Usage: report_and_fix issues_array status_var status_key fix_note setup_script verify_service [args...]
|
||||||
######################################################################
|
######################################################################
|
||||||
report_and_fix() {
|
report_and_fix() {
|
||||||
local -n _issues=$1
|
local -n _issues=$1
|
||||||
local -n _status=$2
|
local -n _status=$2
|
||||||
local status_key="$3"
|
local status_key="$3"
|
||||||
local fix_note="$4"
|
local fix_note="$4"
|
||||||
local setup_script="$5"
|
local setup_script="$5"
|
||||||
local verify_service="${6:-}"
|
local verify_service="${6:-}"
|
||||||
shift 6
|
shift 6
|
||||||
local script_args=("$@")
|
local script_args=("$@")
|
||||||
|
|
||||||
if [[ $_status != "ok" ]]; then
|
if [[ $_status != "ok" ]]; then
|
||||||
for issue in "${_issues[@]}"; do
|
for issue in "${_issues[@]}"; do
|
||||||
if [[ $_status == "error" ]]; then
|
if [[ $_status == "error" ]]; then
|
||||||
err "$issue"
|
err "$issue"
|
||||||
else
|
else
|
||||||
warn "$issue"
|
warn "$issue"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
((ISSUES_FOUND++)) || true
|
((ISSUES_FOUND++)) || true
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 && $_status == "error" ]]; then
|
if [[ $STATUS_ONLY -eq 0 && $_status == "error" ]]; then
|
||||||
note "$fix_note"
|
note "$fix_note"
|
||||||
if [[ -f $setup_script ]]; then
|
if [[ -f $setup_script ]]; then
|
||||||
run bash "$setup_script" "${script_args[@]}"
|
run bash "$setup_script" "${script_args[@]}"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
# Re-verify after fix
|
# 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"
|
_status="ok"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
err "Setup script not found: $setup_script"
|
err "Setup script not found: $setup_script"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SERVICE_STATUS["$status_key"]=$_status
|
SERVICE_STATUS["$status_key"]=$_status
|
||||||
}
|
}
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
@ -167,443 +167,443 @@ report_and_fix() {
|
|||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
check_pacman_wrapper() {
|
check_pacman_wrapper() {
|
||||||
header "Pacman Wrapper"
|
header "Pacman Wrapper"
|
||||||
|
|
||||||
local status="ok"
|
local status="ok"
|
||||||
local issues=()
|
local issues=()
|
||||||
|
|
||||||
# Check if wrapper is installed
|
# Check if wrapper is installed
|
||||||
if [[ -L /usr/bin/pacman ]]; then
|
if [[ -L /usr/bin/pacman ]]; then
|
||||||
local target
|
local target
|
||||||
target=$(readlink -f /usr/bin/pacman)
|
target=$(readlink -f /usr/bin/pacman)
|
||||||
if [[ $target == "/usr/local/bin/pacman_wrapper" ]]; then
|
if [[ $target == "/usr/local/bin/pacman_wrapper" ]]; then
|
||||||
msg "Pacman symlink points to wrapper"
|
msg "Pacman symlink points to wrapper"
|
||||||
else
|
else
|
||||||
issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)")
|
issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
issues+=("Pacman is not a symlink (wrapper not installed)")
|
issues+=("Pacman is not a symlink (wrapper not installed)")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if original pacman is backed up
|
# Check if original pacman is backed up
|
||||||
if [[ -f /usr/bin/pacman.orig ]]; then
|
if [[ -f /usr/bin/pacman.orig ]]; then
|
||||||
msg "Original pacman backed up at /usr/bin/pacman.orig"
|
msg "Original pacman backed up at /usr/bin/pacman.orig"
|
||||||
else
|
else
|
||||||
issues+=("Original pacman backup not found at /usr/bin/pacman.orig")
|
issues+=("Original pacman backup not found at /usr/bin/pacman.orig")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if wrapper script exists
|
# Check if wrapper script exists
|
||||||
if [[ -f /usr/local/bin/pacman_wrapper ]]; then
|
if [[ -f /usr/local/bin/pacman_wrapper ]]; then
|
||||||
msg "Wrapper script exists at /usr/local/bin/pacman_wrapper"
|
msg "Wrapper script exists at /usr/local/bin/pacman_wrapper"
|
||||||
else
|
else
|
||||||
issues+=("Wrapper script not found at /usr/local/bin/pacman_wrapper")
|
issues+=("Wrapper script not found at /usr/local/bin/pacman_wrapper")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check supporting files
|
# Check supporting files
|
||||||
for file in words.txt pacman_blocked_keywords.txt pacman_whitelist.txt; do
|
for file in words.txt pacman_blocked_keywords.txt pacman_whitelist.txt; do
|
||||||
if [[ -f "/usr/local/bin/$file" ]]; then
|
if [[ -f "/usr/local/bin/$file" ]]; then
|
||||||
msg "Supporting file exists: /usr/local/bin/$file"
|
msg "Supporting file exists: /usr/local/bin/$file"
|
||||||
else
|
else
|
||||||
warn "Supporting file missing: /usr/local/bin/$file"
|
warn "Supporting file missing: /usr/local/bin/$file"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Report and fix
|
# Report and fix
|
||||||
if [[ $status == "error" ]]; then
|
if [[ $status == "error" ]]; then
|
||||||
for issue in "${issues[@]}"; do
|
for issue in "${issues[@]}"; do
|
||||||
err "$issue"
|
err "$issue"
|
||||||
done
|
done
|
||||||
((ISSUES_FOUND++)) || true
|
((ISSUES_FOUND++)) || true
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 ]]; then
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
||||||
note "Installing pacman wrapper..."
|
note "Installing pacman wrapper..."
|
||||||
if [[ -f $PACMAN_WRAPPER_INSTALL ]]; then
|
if [[ -f $PACMAN_WRAPPER_INSTALL ]]; then
|
||||||
run bash "$PACMAN_WRAPPER_INSTALL"
|
run bash "$PACMAN_WRAPPER_INSTALL"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
# Re-verify after fix
|
# Re-verify after fix
|
||||||
if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/pacman ]] && [[ -f /usr/bin/pacman.orig ]] && [[ -f /usr/local/bin/pacman_wrapper ]]; then
|
if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/pacman ]] && [[ -f /usr/bin/pacman.orig ]] && [[ -f /usr/local/bin/pacman_wrapper ]]; then
|
||||||
status="ok"
|
status="ok"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
err "Installer script not found: $PACMAN_WRAPPER_INSTALL"
|
err "Installer script not found: $PACMAN_WRAPPER_INSTALL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SERVICE_STATUS["pacman_wrapper"]=$status
|
SERVICE_STATUS["pacman_wrapper"]=$status
|
||||||
}
|
}
|
||||||
|
|
||||||
check_midnight_shutdown() {
|
check_midnight_shutdown() {
|
||||||
header "Midnight Shutdown (Day-Specific Auto-Shutdown)"
|
header "Midnight Shutdown (Day-Specific Auto-Shutdown)"
|
||||||
|
|
||||||
local status="ok"
|
local status="ok"
|
||||||
local issues=()
|
local issues=()
|
||||||
|
|
||||||
# Check timer
|
# 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"
|
msg "day-specific-shutdown.timer is enabled"
|
||||||
else
|
else
|
||||||
issues+=("day-specific-shutdown.timer is not enabled")
|
issues+=("day-specific-shutdown.timer is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
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"
|
msg "day-specific-shutdown.timer is active"
|
||||||
else
|
else
|
||||||
issues+=("day-specific-shutdown.timer is not active")
|
issues+=("day-specific-shutdown.timer is not active")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check service file exists
|
# Check service file exists
|
||||||
if [[ -f /etc/systemd/system/day-specific-shutdown.service ]]; then
|
if [[ -f /etc/systemd/system/day-specific-shutdown.service ]]; then
|
||||||
msg "day-specific-shutdown.service file exists"
|
msg "day-specific-shutdown.service file exists"
|
||||||
else
|
else
|
||||||
issues+=("day-specific-shutdown.service file missing")
|
issues+=("day-specific-shutdown.service file missing")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check management script
|
# Check management script
|
||||||
if [[ -f /usr/local/bin/day-specific-shutdown-manager.sh ]]; then
|
if [[ -f /usr/local/bin/day-specific-shutdown-manager.sh ]]; then
|
||||||
msg "Shutdown manager script exists"
|
msg "Shutdown manager script exists"
|
||||||
else
|
else
|
||||||
issues+=("day-specific-shutdown-manager.sh not found")
|
issues+=("day-specific-shutdown-manager.sh not found")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
report_and_fix issues status "midnight_shutdown" \
|
report_and_fix issues status "midnight_shutdown" \
|
||||||
"Setting up midnight shutdown..." \
|
"Setting up midnight shutdown..." \
|
||||||
"$MIDNIGHT_SHUTDOWN_SCRIPT" \
|
"$MIDNIGHT_SHUTDOWN_SCRIPT" \
|
||||||
"day-specific-shutdown.timer" \
|
"day-specific-shutdown.timer" \
|
||||||
enable
|
enable
|
||||||
}
|
}
|
||||||
|
|
||||||
check_startup_monitor() {
|
check_startup_monitor() {
|
||||||
header "PC Startup Monitor"
|
header "PC Startup Monitor"
|
||||||
|
|
||||||
local status="ok"
|
local status="ok"
|
||||||
local issues=()
|
local issues=()
|
||||||
|
|
||||||
# Check timer (the timer triggers the service, so we check the timer)
|
# 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"
|
msg "pc-startup-monitor.timer is enabled"
|
||||||
else
|
else
|
||||||
issues+=("pc-startup-monitor.timer is not enabled")
|
issues+=("pc-startup-monitor.timer is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
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"
|
msg "pc-startup-monitor.timer is active"
|
||||||
else
|
else
|
||||||
issues+=("pc-startup-monitor.timer is not active")
|
issues+=("pc-startup-monitor.timer is not active")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check service file exists
|
# Check service file exists
|
||||||
if [[ -f /etc/systemd/system/pc-startup-monitor.service ]]; then
|
if [[ -f /etc/systemd/system/pc-startup-monitor.service ]]; then
|
||||||
msg "pc-startup-monitor.service file exists"
|
msg "pc-startup-monitor.service file exists"
|
||||||
else
|
else
|
||||||
issues+=("pc-startup-monitor.service file missing")
|
issues+=("pc-startup-monitor.service file missing")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check monitor script
|
# Check monitor script
|
||||||
if [[ -f /usr/local/bin/pc-startup-check.sh ]]; then
|
if [[ -f /usr/local/bin/pc-startup-check.sh ]]; then
|
||||||
msg "Startup check script exists"
|
msg "Startup check script exists"
|
||||||
else
|
else
|
||||||
issues+=("pc-startup-check.sh not found")
|
issues+=("pc-startup-check.sh not found")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
report_and_fix issues status "startup_monitor" \
|
report_and_fix issues status "startup_monitor" \
|
||||||
"Setting up startup monitor..." \
|
"Setting up startup monitor..." \
|
||||||
"$STARTUP_MONITOR_SCRIPT" \
|
"$STARTUP_MONITOR_SCRIPT" \
|
||||||
"pc-startup-monitor.timer"
|
"pc-startup-monitor.timer"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_periodic_systems() {
|
check_periodic_systems() {
|
||||||
header "Periodic System Maintenance"
|
header "Periodic System Maintenance"
|
||||||
|
|
||||||
local status="ok"
|
local status="ok"
|
||||||
local issues=()
|
local issues=()
|
||||||
|
|
||||||
# Check timer
|
# 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"
|
msg "periodic-system-maintenance.timer is enabled"
|
||||||
else
|
else
|
||||||
issues+=("periodic-system-maintenance.timer is not enabled")
|
issues+=("periodic-system-maintenance.timer is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
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"
|
msg "periodic-system-maintenance.timer is active"
|
||||||
else
|
else
|
||||||
issues+=("periodic-system-maintenance.timer is not active")
|
issues+=("periodic-system-maintenance.timer is not active")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check startup service
|
# 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"
|
msg "periodic-system-startup.service is enabled"
|
||||||
else
|
else
|
||||||
issues+=("periodic-system-startup.service is not enabled")
|
issues+=("periodic-system-startup.service is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check hosts file monitor
|
# 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"
|
msg "hosts-file-monitor.service is enabled"
|
||||||
else
|
else
|
||||||
issues+=("hosts-file-monitor.service is not enabled")
|
issues+=("hosts-file-monitor.service is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
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"
|
msg "hosts-file-monitor.service is active"
|
||||||
else
|
else
|
||||||
issues+=("hosts-file-monitor.service is not active")
|
issues+=("hosts-file-monitor.service is not active")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check maintenance script
|
# Check maintenance script
|
||||||
if [[ -f /usr/local/bin/periodic-system-maintenance.sh ]]; then
|
if [[ -f /usr/local/bin/periodic-system-maintenance.sh ]]; then
|
||||||
msg "Maintenance script exists"
|
msg "Maintenance script exists"
|
||||||
else
|
else
|
||||||
issues+=("periodic-system-maintenance.sh not found")
|
issues+=("periodic-system-maintenance.sh not found")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
report_and_fix issues status "periodic_systems" \
|
report_and_fix issues status "periodic_systems" \
|
||||||
"Setting up periodic systems..." \
|
"Setting up periodic systems..." \
|
||||||
"$PERIODIC_SYSTEM_SCRIPT" \
|
"$PERIODIC_SYSTEM_SCRIPT" \
|
||||||
"periodic-system-maintenance.timer"
|
"periodic-system-maintenance.timer"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_hosts() {
|
check_hosts() {
|
||||||
header "Hosts File and Guards"
|
header "Hosts File and Guards"
|
||||||
|
|
||||||
local status="ok"
|
local status="ok"
|
||||||
local issues=()
|
local issues=()
|
||||||
|
|
||||||
# Check /etc/hosts exists and has content
|
# Check /etc/hosts exists and has content
|
||||||
if [[ -f /etc/hosts ]]; then
|
if [[ -f /etc/hosts ]]; then
|
||||||
local line_count
|
local line_count
|
||||||
line_count=$(wc -l </etc/hosts)
|
line_count=$(wc -l < /etc/hosts)
|
||||||
if [[ $line_count -gt 100 ]]; then
|
if [[ $line_count -gt 100 ]]; then
|
||||||
msg "/etc/hosts exists with $line_count lines (StevenBlack list likely installed)"
|
msg "/etc/hosts exists with $line_count lines (StevenBlack list likely installed)"
|
||||||
else
|
else
|
||||||
issues+=("/etc/hosts has only $line_count lines (StevenBlack list may not be installed)")
|
issues+=("/etc/hosts has only $line_count lines (StevenBlack list may not be installed)")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
issues+=("/etc/hosts does not exist")
|
issues+=("/etc/hosts does not exist")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if hosts file is immutable
|
# Check if hosts file is immutable
|
||||||
local attrs
|
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
|
if [[ $attrs == *"i"* ]]; then
|
||||||
msg "/etc/hosts has immutable attribute set"
|
msg "/etc/hosts has immutable attribute set"
|
||||||
else
|
else
|
||||||
issues+=("/etc/hosts is not immutable")
|
issues+=("/etc/hosts is not immutable")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check cached hosts file
|
# Check cached hosts file
|
||||||
if [[ -f /etc/hosts.stevenblack ]]; then
|
if [[ -f /etc/hosts.stevenblack ]]; then
|
||||||
msg "StevenBlack cache exists at /etc/hosts.stevenblack"
|
msg "StevenBlack cache exists at /etc/hosts.stevenblack"
|
||||||
else
|
else
|
||||||
issues+=("StevenBlack cache not found")
|
issues+=("StevenBlack cache not found")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check hosts guard path watcher
|
# 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"
|
msg "hosts-guard.path is enabled"
|
||||||
else
|
else
|
||||||
issues+=("hosts-guard.path is not enabled")
|
issues+=("hosts-guard.path is not enabled")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
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"
|
msg "hosts-guard.path is active"
|
||||||
else
|
else
|
||||||
issues+=("hosts-guard.path is not active")
|
issues+=("hosts-guard.path is not active")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check hosts bind mount service
|
# 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"
|
msg "hosts-bind-mount.service is enabled"
|
||||||
else
|
else
|
||||||
issues+=("hosts-bind-mount.service is not enabled")
|
issues+=("hosts-bind-mount.service is not enabled")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check enforcement script
|
# Check enforcement script
|
||||||
if [[ -f /usr/local/sbin/enforce-hosts.sh ]]; then
|
if [[ -f /usr/local/sbin/enforce-hosts.sh ]]; then
|
||||||
msg "Enforcement script exists at /usr/local/sbin/enforce-hosts.sh"
|
msg "Enforcement script exists at /usr/local/sbin/enforce-hosts.sh"
|
||||||
else
|
else
|
||||||
issues+=("enforce-hosts.sh not found")
|
issues+=("enforce-hosts.sh not found")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check unlock script
|
# Check unlock script
|
||||||
if [[ -f /usr/local/sbin/unlock-hosts ]]; then
|
if [[ -f /usr/local/sbin/unlock-hosts ]]; then
|
||||||
msg "Unlock script exists at /usr/local/sbin/unlock-hosts"
|
msg "Unlock script exists at /usr/local/sbin/unlock-hosts"
|
||||||
else
|
else
|
||||||
issues+=("unlock-hosts not found")
|
issues+=("unlock-hosts not found")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check locked hosts snapshot
|
# Check locked hosts snapshot
|
||||||
if [[ -f /usr/local/share/locked-hosts ]]; then
|
if [[ -f /usr/local/share/locked-hosts ]]; then
|
||||||
msg "Canonical hosts snapshot exists at /usr/local/share/locked-hosts"
|
msg "Canonical hosts snapshot exists at /usr/local/share/locked-hosts"
|
||||||
else
|
else
|
||||||
issues+=("Canonical hosts snapshot not found")
|
issues+=("Canonical hosts snapshot not found")
|
||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check pacman hooks
|
# Check pacman hooks
|
||||||
if [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]] && [[ -f /etc/pacman.d/hooks/90-relock-etc-hosts.hook ]]; then
|
if [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]] && [[ -f /etc/pacman.d/hooks/90-relock-etc-hosts.hook ]]; then
|
||||||
msg "Pacman hooks installed"
|
msg "Pacman hooks installed"
|
||||||
else
|
else
|
||||||
issues+=("Pacman hooks not installed")
|
issues+=("Pacman hooks not installed")
|
||||||
status="warning"
|
status="warning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Report issues
|
# Report issues
|
||||||
if [[ $status != "ok" ]]; then
|
if [[ $status != "ok" ]]; then
|
||||||
for issue in "${issues[@]}"; do
|
for issue in "${issues[@]}"; do
|
||||||
if [[ $status == "error" ]]; then
|
if [[ $status == "error" ]]; then
|
||||||
err "$issue"
|
err "$issue"
|
||||||
else
|
else
|
||||||
warn "$issue"
|
warn "$issue"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
((ISSUES_FOUND++)) || true
|
((ISSUES_FOUND++)) || true
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 ]]; then
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
||||||
# Run hosts install first
|
# 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..."
|
note "Installing hosts file..."
|
||||||
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_INSTALL_SCRIPT"
|
run bash "$HOSTS_INSTALL_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
err "Hosts install script not found: $HOSTS_INSTALL_SCRIPT"
|
err "Hosts install script not found: $HOSTS_INSTALL_SCRIPT"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run hosts guard setup
|
# 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..."
|
note "Setting up hosts guard..."
|
||||||
if [[ -f $HOSTS_GUARD_SCRIPT ]]; then
|
if [[ -f $HOSTS_GUARD_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_GUARD_SCRIPT"
|
run bash "$HOSTS_GUARD_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
err "Hosts guard script not found: $HOSTS_GUARD_SCRIPT"
|
err "Hosts guard script not found: $HOSTS_GUARD_SCRIPT"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install pacman hooks if missing
|
# Install pacman hooks if missing
|
||||||
if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
||||||
note "Installing pacman hooks..."
|
note "Installing pacman hooks..."
|
||||||
if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then
|
if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_PACMAN_HOOKS_SCRIPT"
|
run bash "$HOSTS_PACMAN_HOOKS_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
err "Pacman hooks script not found: $HOSTS_PACMAN_HOOKS_SCRIPT"
|
err "Pacman hooks script not found: $HOSTS_PACMAN_HOOKS_SCRIPT"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Re-verify after fixes
|
# Re-verify after fixes
|
||||||
if [[ $DRY_RUN -eq 0 ]]; then
|
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/sbin/enforce-hosts.sh ]] &&
|
||||||
[[ -f /usr/local/share/locked-hosts ]] &&
|
[[ -f /usr/local/share/locked-hosts ]] &&
|
||||||
[[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
[[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
||||||
# Downgrade to warning if only minor issues remain (immutable attr, etc.)
|
# Downgrade to warning if only minor issues remain (immutable attr, etc.)
|
||||||
status="ok"
|
status="ok"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SERVICE_STATUS["hosts"]=$status
|
SERVICE_STATUS["hosts"]=$status
|
||||||
}
|
}
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Summary
|
# Summary
|
||||||
######################################################################
|
######################################################################
|
||||||
print_summary() {
|
print_summary() {
|
||||||
header "Summary"
|
header "Summary"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
printf "%-25s %s\n" "Service" "Status"
|
printf "%-25s %s\n" "Service" "Status"
|
||||||
printf "%-25s %s\n" "-------" "------"
|
printf "%-25s %s\n" "-------" "------"
|
||||||
|
|
||||||
for service in pacman_wrapper midnight_shutdown startup_monitor periodic_systems hosts; do
|
for service in pacman_wrapper midnight_shutdown startup_monitor periodic_systems hosts; do
|
||||||
local status="${SERVICE_STATUS[$service]:-unknown}"
|
local status="${SERVICE_STATUS[$service]:-unknown}"
|
||||||
local color
|
local color
|
||||||
case "$status" in
|
case "$status" in
|
||||||
ok) color=$GREEN ;;
|
ok) color=$GREEN ;;
|
||||||
warning) color=$YELLOW ;;
|
warning) color=$YELLOW ;;
|
||||||
error) color=$RED ;;
|
error) color=$RED ;;
|
||||||
*) color=$NC ;;
|
*) color=$NC ;;
|
||||||
esac
|
esac
|
||||||
printf "%-25s ${color}%s${NC}\n" "$service" "$status"
|
printf "%-25s ${color}%s${NC}\n" "$service" "$status"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if [[ $DRY_RUN -eq 1 ]]; then
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
note "DRY RUN - No changes were made"
|
note "DRY RUN - No changes were made"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $ISSUES_FOUND -eq 0 ]]; then
|
if [[ $ISSUES_FOUND -eq 0 ]]; then
|
||||||
msg "All services are properly configured!"
|
msg "All services are properly configured!"
|
||||||
else
|
else
|
||||||
if [[ $STATUS_ONLY -eq 1 ]]; then
|
if [[ $STATUS_ONLY -eq 1 ]]; then
|
||||||
warn "Found $ISSUES_FOUND service(s) with issues"
|
warn "Found $ISSUES_FOUND service(s) with issues"
|
||||||
note "Run without --status to fix issues"
|
note "Run without --status to fix issues"
|
||||||
else
|
else
|
||||||
if [[ $FIXES_APPLIED -gt 0 ]]; then
|
if [[ $FIXES_APPLIED -gt 0 ]]; then
|
||||||
msg "Applied $FIXES_APPLIED fix(es)"
|
msg "Applied $FIXES_APPLIED fix(es)"
|
||||||
else
|
else
|
||||||
warn "Found $ISSUES_FOUND issue(s) but no fixes were applied"
|
warn "Found $ISSUES_FOUND issue(s) but no fixes were applied"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Main
|
# Main
|
||||||
######################################################################
|
######################################################################
|
||||||
main() {
|
main() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "Digital Wellbeing Services Status Check"
|
echo "Digital Wellbeing Services Status Check"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "Date: $(date)"
|
echo "Date: $(date)"
|
||||||
echo "User: ${SUDO_USER:-$USER}"
|
echo "User: ${SUDO_USER:-$USER}"
|
||||||
if [[ $DRY_RUN -eq 1 ]]; then
|
if [[ $DRY_RUN -eq 1 ]]; then
|
||||||
echo "Mode: DRY RUN (no changes will be made)"
|
echo "Mode: DRY RUN (no changes will be made)"
|
||||||
elif [[ $STATUS_ONLY -eq 1 ]]; then
|
elif [[ $STATUS_ONLY -eq 1 ]]; then
|
||||||
echo "Mode: STATUS ONLY (no changes will be made)"
|
echo "Mode: STATUS ONLY (no changes will be made)"
|
||||||
else
|
else
|
||||||
echo "Mode: CHECK AND FIX"
|
echo "Mode: CHECK AND FIX"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
check_pacman_wrapper
|
check_pacman_wrapper
|
||||||
check_midnight_shutdown
|
check_midnight_shutdown
|
||||||
check_startup_monitor
|
check_startup_monitor
|
||||||
check_periodic_systems
|
check_periodic_systems
|
||||||
check_hosts
|
check_hosts
|
||||||
|
|
||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
|||||||
@ -12,14 +12,14 @@ set -euo pipefail
|
|||||||
# Send desktop notification (inlined from common.sh to avoid dependency issues
|
# Send desktop notification (inlined from common.sh to avoid dependency issues
|
||||||
# when script is installed to /usr/local/bin)
|
# when script is installed to /usr/local/bin)
|
||||||
notify() {
|
notify() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
local message="$2"
|
local message="$2"
|
||||||
local urgency="${3:-normal}"
|
local urgency="${3:-normal}"
|
||||||
local timeout="${4:-5000}"
|
local timeout="${4:-5000}"
|
||||||
|
|
||||||
if command -v notify-send &>/dev/null; then
|
if command -v notify-send &> /dev/null; then
|
||||||
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true
|
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@ -29,165 +29,165 @@ LOG_FILE="$STATE_DIR/compulsive-block.log"
|
|||||||
# Apps to limit (name -> binary path)
|
# Apps to limit (name -> binary path)
|
||||||
# These are the primary wrapper locations (what the user calls)
|
# These are the primary wrapper locations (what the user calls)
|
||||||
declare -A APPS=(
|
declare -A APPS=(
|
||||||
["beeper"]="/usr/bin/beeper"
|
["beeper"]="/usr/bin/beeper"
|
||||||
["signal-desktop"]="/usr/bin/signal-desktop"
|
["signal-desktop"]="/usr/bin/signal-desktop"
|
||||||
["discord"]="/usr/bin/discord"
|
["discord"]="/usr/bin/discord"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Actual executable paths (the real binaries to exec after wrapper check)
|
# Actual executable paths (the real binaries to exec after wrapper check)
|
||||||
# These are where the real code lives
|
# These are where the real code lives
|
||||||
declare -A REAL_BINARIES=(
|
declare -A REAL_BINARIES=(
|
||||||
["beeper"]="/opt/beeper/beepertexts"
|
["beeper"]="/opt/beeper/beepertexts"
|
||||||
["signal-desktop"]="/usr/lib/signal-desktop/signal-desktop"
|
["signal-desktop"]="/usr/lib/signal-desktop/signal-desktop"
|
||||||
["discord"]="/opt/discord/Discord"
|
["discord"]="/opt/discord/Discord"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure state directory exists
|
# Ensure state directory exists
|
||||||
ensure_state_dir() {
|
ensure_state_dir() {
|
||||||
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
mkdir -p "$STATE_DIR" 2> /dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log message with timestamp
|
# Log message with timestamp
|
||||||
log_message() {
|
log_message() {
|
||||||
local msg
|
local msg
|
||||||
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
|
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
|
||||||
echo "$msg" >&2
|
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)
|
# Get current hour key (YYYY-MM-DD-HH format)
|
||||||
get_hour_key() {
|
get_hour_key() {
|
||||||
date '+%Y-%m-%d-%H'
|
date '+%Y-%m-%d-%H'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get state file path for an app
|
# Get state file path for an app
|
||||||
get_state_file() {
|
get_state_file() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
echo "$STATE_DIR/${app}.lastopen"
|
echo "$STATE_DIR/${app}.lastopen"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if app was already opened this hour
|
# Check if app was already opened this hour
|
||||||
was_opened_this_hour() {
|
was_opened_this_hour() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local state_file
|
local state_file
|
||||||
state_file=$(get_state_file "$app")
|
state_file=$(get_state_file "$app")
|
||||||
local current_hour
|
local current_hour
|
||||||
current_hour=$(get_hour_key)
|
current_hour=$(get_hour_key)
|
||||||
|
|
||||||
if [[ -f $state_file ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
local last_hour
|
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
|
if [[ $last_hour == "$current_hour" ]]; then
|
||||||
return 0 # Was opened this hour
|
return 0 # Was opened this hour
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
return 1 # Not opened this hour
|
return 1 # Not opened this hour
|
||||||
}
|
}
|
||||||
|
|
||||||
# Record app opening
|
# Record app opening
|
||||||
record_opening() {
|
record_opening() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local state_file
|
local state_file
|
||||||
state_file=$(get_state_file "$app")
|
state_file=$(get_state_file "$app")
|
||||||
local current_hour
|
local current_hour
|
||||||
current_hour=$(get_hour_key)
|
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)"
|
log_message "ALLOWED: $app opened (first time this hour: $current_hour)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Block app and notify
|
# Block app and notify
|
||||||
block_app() {
|
block_app() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local current_hour
|
local current_hour
|
||||||
current_hour=$(get_hour_key)
|
current_hour=$(get_hour_key)
|
||||||
|
|
||||||
log_message "BLOCKED: $app launch prevented (already opened this hour: $current_hour)"
|
log_message "BLOCKED: $app launch prevented (already opened this hour: $current_hour)"
|
||||||
|
|
||||||
# Send notification using common library
|
# Send notification using common library
|
||||||
notify "🚫 $app Blocked" "Already opened this hour. Wait until the next hour." critical 5000
|
notify "🚫 $app Blocked" "Already opened this hour. Wait until the next hour." critical 5000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get real binary path for an app
|
# Get real binary path for an app
|
||||||
get_real_binary() {
|
get_real_binary() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
local real_binary="${REAL_BINARIES[$app]}"
|
local real_binary="${REAL_BINARIES[$app]}"
|
||||||
|
|
||||||
# Check if wrapper is installed (original moved to .orig)
|
# Check if wrapper is installed (original moved to .orig)
|
||||||
if [[ -f "${wrapper_path}.orig" ]]; then
|
if [[ -f "${wrapper_path}.orig" ]]; then
|
||||||
# Wrapper installed, return the actual executable
|
# Wrapper installed, return the actual executable
|
||||||
echo "$real_binary"
|
echo "$real_binary"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main wrapper function - called when wrapping app launches
|
# Main wrapper function - called when wrapping app launches
|
||||||
wrapper_main() {
|
wrapper_main() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
shift
|
shift
|
||||||
|
|
||||||
ensure_state_dir
|
ensure_state_dir
|
||||||
|
|
||||||
local real_binary
|
local real_binary
|
||||||
if ! real_binary=$(get_real_binary "$app"); then
|
if ! real_binary=$(get_real_binary "$app"); then
|
||||||
log_message "ERROR: Real binary not found for $app"
|
log_message "ERROR: Real binary not found for $app"
|
||||||
echo "Error: Real binary for $app not found. Was the installer run?" >&2
|
echo "Error: Real binary for $app not found. Was the installer run?" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if was_opened_this_hour "$app"; then
|
if was_opened_this_hour "$app"; then
|
||||||
block_app "$app"
|
block_app "$app"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
record_opening "$app"
|
record_opening "$app"
|
||||||
exec "$real_binary" "$@"
|
exec "$real_binary" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install wrapper for a specific app
|
# Install wrapper for a specific app
|
||||||
install_wrapper() {
|
install_wrapper() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
local real_binary="${REAL_BINARIES[$app]}"
|
local real_binary="${REAL_BINARIES[$app]}"
|
||||||
|
|
||||||
# Check if already wrapped
|
# Check if already wrapped
|
||||||
if [[ -f "${wrapper_path}.orig" ]]; then
|
if [[ -f "${wrapper_path}.orig" ]]; then
|
||||||
echo " ✓ $app already wrapped"
|
echo " ✓ $app already wrapped"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if wrapper location exists (file or symlink)
|
# Check if wrapper location exists (file or symlink)
|
||||||
if [[ ! -e $wrapper_path && ! -L $wrapper_path ]]; then
|
if [[ ! -e $wrapper_path && ! -L $wrapper_path ]]; then
|
||||||
echo " ⚠ $app not installed ($wrapper_path not found)"
|
echo " ⚠ $app not installed ($wrapper_path not found)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if real binary exists
|
# Check if real binary exists
|
||||||
if [[ ! -x $real_binary ]]; then
|
if [[ ! -x $real_binary ]]; then
|
||||||
echo " ⚠ $app real binary not found ($real_binary)"
|
echo " ⚠ $app real binary not found ($real_binary)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " Installing wrapper for $app..."
|
echo " Installing wrapper for $app..."
|
||||||
|
|
||||||
# Handle symlinks: save the symlink itself, not the target
|
# Handle symlinks: save the symlink itself, not the target
|
||||||
if [[ -L $wrapper_path ]]; then
|
if [[ -L $wrapper_path ]]; then
|
||||||
local link_target
|
local link_target
|
||||||
link_target=$(readlink "$wrapper_path")
|
link_target=$(readlink "$wrapper_path")
|
||||||
echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig"
|
echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig"
|
||||||
# Remove symlink and create .orig that stores the link target info
|
# 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"
|
rm "$wrapper_path"
|
||||||
else
|
else
|
||||||
echo " Backing up $wrapper_path -> ${wrapper_path}.orig"
|
echo " Backing up $wrapper_path -> ${wrapper_path}.orig"
|
||||||
mv "$wrapper_path" "${wrapper_path}.orig"
|
mv "$wrapper_path" "${wrapper_path}.orig"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " Creating wrapper at $wrapper_path"
|
echo " Creating wrapper at $wrapper_path"
|
||||||
cat >"$wrapper_path" <<WRAPPER_EOF
|
cat > "$wrapper_path" << WRAPPER_EOF
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Auto-generated wrapper for $app - blocks compulsive opening
|
# Auto-generated wrapper for $app - blocks compulsive opening
|
||||||
# Real binary: $real_binary
|
# Real binary: $real_binary
|
||||||
@ -195,88 +195,88 @@ install_wrapper() {
|
|||||||
exec /usr/local/bin/block-compulsive-opening.sh wrapper "$app" "\$@"
|
exec /usr/local/bin/block-compulsive-opening.sh wrapper "$app" "\$@"
|
||||||
WRAPPER_EOF
|
WRAPPER_EOF
|
||||||
|
|
||||||
chmod +x "$wrapper_path"
|
chmod +x "$wrapper_path"
|
||||||
echo " ✓ $app wrapper installed"
|
echo " ✓ $app wrapper installed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Uninstall wrapper for a specific app
|
# Uninstall wrapper for a specific app
|
||||||
uninstall_wrapper() {
|
uninstall_wrapper() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
|
|
||||||
if [[ ! -f "${wrapper_path}.orig" ]]; then
|
if [[ ! -f "${wrapper_path}.orig" ]]; then
|
||||||
echo " ⚠ $app wrapper not found"
|
echo " ⚠ $app wrapper not found"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " Removing wrapper for $app..."
|
echo " Removing wrapper for $app..."
|
||||||
rm -f "$wrapper_path"
|
rm -f "$wrapper_path"
|
||||||
|
|
||||||
# Check if it was a symlink (stored as SYMLINK:target in .orig)
|
# Check if it was a symlink (stored as SYMLINK:target in .orig)
|
||||||
local orig_content
|
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
|
if [[ $orig_content == SYMLINK:* ]]; then
|
||||||
local link_target="${orig_content#SYMLINK:}"
|
local link_target="${orig_content#SYMLINK:}"
|
||||||
echo " Restoring symlink $wrapper_path -> $link_target"
|
echo " Restoring symlink $wrapper_path -> $link_target"
|
||||||
ln -s "$link_target" "$wrapper_path"
|
ln -s "$link_target" "$wrapper_path"
|
||||||
rm "${wrapper_path}.orig"
|
rm "${wrapper_path}.orig"
|
||||||
else
|
else
|
||||||
echo " Restoring original file"
|
echo " Restoring original file"
|
||||||
mv "${wrapper_path}.orig" "$wrapper_path"
|
mv "${wrapper_path}.orig" "$wrapper_path"
|
||||||
fi
|
fi
|
||||||
echo " ✓ $app restored"
|
echo " ✓ $app restored"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install all wrappers
|
# Install all wrappers
|
||||||
install_all() {
|
install_all() {
|
||||||
echo "Installing compulsive opening blockers..."
|
echo "Installing compulsive opening blockers..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Install main script to /usr/local/bin
|
# Install main script to /usr/local/bin
|
||||||
local script_path
|
local script_path
|
||||||
script_path="$(readlink -f "$0")"
|
script_path="$(readlink -f "$0")"
|
||||||
local install_path="/usr/local/bin/block-compulsive-opening.sh"
|
local install_path="/usr/local/bin/block-compulsive-opening.sh"
|
||||||
|
|
||||||
if [[ $script_path != "$install_path" ]]; then
|
if [[ $script_path != "$install_path" ]]; then
|
||||||
echo "Installing main script to $install_path..."
|
echo "Installing main script to $install_path..."
|
||||||
cp "$script_path" "$install_path"
|
cp "$script_path" "$install_path"
|
||||||
chmod +x "$install_path"
|
chmod +x "$install_path"
|
||||||
echo "✓ Main script installed"
|
echo "✓ Main script installed"
|
||||||
else
|
else
|
||||||
echo "Main script already at $install_path"
|
echo "Main script already at $install_path"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Install wrappers for each app
|
# Install wrappers for each app
|
||||||
local installed=0
|
local installed=0
|
||||||
for app in "${!APPS[@]}"; do
|
for app in "${!APPS[@]}"; do
|
||||||
if install_wrapper "$app"; then
|
if install_wrapper "$app"; then
|
||||||
((installed++)) || true
|
((installed++)) || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installation complete. $installed app(s) wrapped."
|
echo "Installation complete. $installed app(s) wrapped."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Each app can now only be opened once per hour."
|
echo "Each app can now only be opened once per hour."
|
||||||
echo "State files stored in: $STATE_DIR"
|
echo "State files stored in: $STATE_DIR"
|
||||||
echo "Logs stored in: $LOG_FILE"
|
echo "Logs stored in: $LOG_FILE"
|
||||||
|
|
||||||
# Install pacman hook to re-wrap after package updates
|
# Install pacman hook to re-wrap after package updates
|
||||||
install_pacman_hook
|
install_pacman_hook
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install pacman hook to re-install wrappers after package updates
|
# Install pacman hook to re-install wrappers after package updates
|
||||||
install_pacman_hook() {
|
install_pacman_hook() {
|
||||||
local hook_dir="/etc/pacman.d/hooks"
|
local hook_dir="/etc/pacman.d/hooks"
|
||||||
local hook_file="$hook_dir/95-compulsive-block-rewrap.hook"
|
local hook_file="$hook_dir/95-compulsive-block-rewrap.hook"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installing pacman hook..."
|
echo "Installing pacman hook..."
|
||||||
|
|
||||||
mkdir -p "$hook_dir"
|
mkdir -p "$hook_dir"
|
||||||
|
|
||||||
cat >"$hook_file" <<'HOOK_EOF'
|
cat > "$hook_file" << 'HOOK_EOF'
|
||||||
[Trigger]
|
[Trigger]
|
||||||
Operation = Upgrade
|
Operation = Upgrade
|
||||||
Operation = Install
|
Operation = Install
|
||||||
@ -291,131 +291,131 @@ When = PostTransaction
|
|||||||
Exec = /usr/local/bin/block-compulsive-opening.sh rewrap-quiet
|
Exec = /usr/local/bin/block-compulsive-opening.sh rewrap-quiet
|
||||||
HOOK_EOF
|
HOOK_EOF
|
||||||
|
|
||||||
chmod 644 "$hook_file"
|
chmod 644 "$hook_file"
|
||||||
echo "✓ Pacman hook installed: $hook_file"
|
echo "✓ Pacman hook installed: $hook_file"
|
||||||
echo " Wrappers will be automatically re-installed after beeper/signal/discord updates"
|
echo " Wrappers will be automatically re-installed after beeper/signal/discord updates"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Uninstall pacman hook
|
# Uninstall pacman hook
|
||||||
uninstall_pacman_hook() {
|
uninstall_pacman_hook() {
|
||||||
local hook_file="/etc/pacman.d/hooks/95-compulsive-block-rewrap.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"
|
rm -f "$hook_file"
|
||||||
echo "✓ Pacman hook removed"
|
echo "✓ Pacman hook removed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Quietly re-wrap apps (for pacman hook - no interactive output)
|
# Quietly re-wrap apps (for pacman hook - no interactive output)
|
||||||
rewrap_quiet() {
|
rewrap_quiet() {
|
||||||
log_message "REWRAP: Pacman hook triggered, re-installing wrappers"
|
log_message "REWRAP: Pacman hook triggered, re-installing wrappers"
|
||||||
|
|
||||||
for app in "${!APPS[@]}"; do
|
for app in "${!APPS[@]}"; do
|
||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
|
|
||||||
# Check if wrapper was overwritten (no longer our wrapper script)
|
# 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
|
# Wrapper was overwritten by package update
|
||||||
log_message "REWRAP: $app wrapper was overwritten, re-installing"
|
log_message "REWRAP: $app wrapper was overwritten, re-installing"
|
||||||
|
|
||||||
# Remove old .orig if exists (it's now stale)
|
# Remove old .orig if exists (it's now stale)
|
||||||
rm -f "${wrapper_path}.orig"
|
rm -f "${wrapper_path}.orig"
|
||||||
|
|
||||||
# Re-install wrapper
|
# Re-install wrapper
|
||||||
install_wrapper "$app" >>"$LOG_FILE" 2>&1 || true
|
install_wrapper "$app" >> "$LOG_FILE" 2>&1 || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
log_message "REWRAP: Complete"
|
log_message "REWRAP: Complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Uninstall all wrappers
|
# Uninstall all wrappers
|
||||||
uninstall_all() {
|
uninstall_all() {
|
||||||
echo "Removing compulsive opening blockers..."
|
echo "Removing compulsive opening blockers..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
for app in "${!APPS[@]}"; do
|
for app in "${!APPS[@]}"; do
|
||||||
uninstall_wrapper "$app" || true
|
uninstall_wrapper "$app" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
rm -f "/usr/local/bin/block-compulsive-opening.sh"
|
rm -f "/usr/local/bin/block-compulsive-opening.sh"
|
||||||
|
|
||||||
# Remove pacman hook
|
# Remove pacman hook
|
||||||
uninstall_pacman_hook
|
uninstall_pacman_hook
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Uninstallation complete."
|
echo "Uninstallation complete."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show status of all apps
|
# Show status of all apps
|
||||||
show_status() {
|
show_status() {
|
||||||
ensure_state_dir
|
ensure_state_dir
|
||||||
local current_hour
|
local current_hour
|
||||||
current_hour=$(get_hour_key)
|
current_hour=$(get_hour_key)
|
||||||
|
|
||||||
echo "Compulsive Opening Blocker Status"
|
echo "Compulsive Opening Blocker Status"
|
||||||
echo "=================================="
|
echo "=================================="
|
||||||
echo "Current hour: $current_hour"
|
echo "Current hour: $current_hour"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
for app in "${!APPS[@]}"; do
|
for app in "${!APPS[@]}"; do
|
||||||
local state_file
|
local state_file
|
||||||
state_file=$(get_state_file "$app")
|
state_file=$(get_state_file "$app")
|
||||||
local status="not opened this hour"
|
local status="not opened this hour"
|
||||||
local icon="○"
|
local icon="○"
|
||||||
|
|
||||||
if [[ -f $state_file ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
local last_hour
|
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
|
if [[ $last_hour == "$current_hour" ]]; then
|
||||||
status="already opened (blocked until next hour)"
|
status="already opened (blocked until next hour)"
|
||||||
icon="●"
|
icon="●"
|
||||||
else
|
else
|
||||||
status="last opened: $last_hour"
|
status="last opened: $last_hour"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if wrapped
|
# Check if wrapped
|
||||||
local wrapped="not installed"
|
local wrapped="not installed"
|
||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
if [[ -f "${wrapper_path}.orig" ]]; then
|
if [[ -f "${wrapper_path}.orig" ]]; then
|
||||||
wrapped="wrapped"
|
wrapped="wrapped"
|
||||||
elif [[ -f $wrapper_path ]]; then
|
elif [[ -f $wrapper_path ]]; then
|
||||||
wrapped="installed (not wrapped)"
|
wrapped="installed (not wrapped)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf " %s %-15s [%s] - %s\n" "$icon" "$app" "$wrapped" "$status"
|
printf " %s %-15s [%s] - %s\n" "$icon" "$app" "$wrapped" "$status"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "State directory: $STATE_DIR"
|
echo "State directory: $STATE_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reset state for an app (allow opening again)
|
# Reset state for an app (allow opening again)
|
||||||
reset_app() {
|
reset_app() {
|
||||||
local app="$1"
|
local app="$1"
|
||||||
local state_file
|
local state_file
|
||||||
state_file=$(get_state_file "$app")
|
state_file=$(get_state_file "$app")
|
||||||
|
|
||||||
if [[ -f $state_file ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
rm -f "$state_file"
|
rm -f "$state_file"
|
||||||
echo "Reset $app - can be opened again this hour"
|
echo "Reset $app - can be opened again this hour"
|
||||||
log_message "RESET: $app state cleared by user"
|
log_message "RESET: $app state cleared by user"
|
||||||
else
|
else
|
||||||
echo "$app was not marked as opened"
|
echo "$app was not marked as opened"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clear all state
|
# Clear all state
|
||||||
reset_all() {
|
reset_all() {
|
||||||
ensure_state_dir
|
ensure_state_dir
|
||||||
rm -f "$STATE_DIR"/*.lastopen
|
rm -f "$STATE_DIR"/*.lastopen
|
||||||
echo "All apps reset - can be opened again this hour"
|
echo "All apps reset - can be opened again this hour"
|
||||||
log_message "RESET: All app states cleared by user"
|
log_message "RESET: All app states cleared by user"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show usage
|
# Show usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Block Compulsive Opening Script
|
Block Compulsive Opening Script
|
||||||
================================
|
================================
|
||||||
|
|
||||||
@ -447,60 +447,60 @@ EOF
|
|||||||
|
|
||||||
# Main entry point
|
# Main entry point
|
||||||
main() {
|
main() {
|
||||||
case "${1:-help}" in
|
case "${1:-help}" in
|
||||||
install)
|
install)
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "Error: install requires root privileges"
|
echo "Error: install requires root privileges"
|
||||||
echo "Run: sudo $0 install"
|
echo "Run: sudo $0 install"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
install_all
|
install_all
|
||||||
;;
|
;;
|
||||||
uninstall)
|
uninstall)
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "Error: uninstall requires root privileges"
|
echo "Error: uninstall requires root privileges"
|
||||||
echo "Run: sudo $0 uninstall"
|
echo "Run: sudo $0 uninstall"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
uninstall_all
|
uninstall_all
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
show_status
|
show_status
|
||||||
;;
|
;;
|
||||||
reset)
|
reset)
|
||||||
if [[ -z ${2:-} ]]; then
|
if [[ -z ${2:-} ]]; then
|
||||||
echo "Error: specify app to reset"
|
echo "Error: specify app to reset"
|
||||||
echo "Apps: ${!APPS[*]}"
|
echo "Apps: ${!APPS[*]}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
reset_app "$2"
|
reset_app "$2"
|
||||||
;;
|
;;
|
||||||
reset-all)
|
reset-all)
|
||||||
reset_all
|
reset_all
|
||||||
;;
|
;;
|
||||||
rewrap-quiet)
|
rewrap-quiet)
|
||||||
# Called by pacman hook - quietly re-wrap apps after package updates
|
# Called by pacman hook - quietly re-wrap apps after package updates
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
rewrap_quiet
|
rewrap_quiet
|
||||||
;;
|
;;
|
||||||
wrapper)
|
wrapper)
|
||||||
if [[ -z ${2:-} ]]; then
|
if [[ -z ${2:-} ]]; then
|
||||||
echo "Error: wrapper requires app name"
|
echo "Error: wrapper requires app name"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
wrapper_main "${@:2}"
|
wrapper_main "${@:2}"
|
||||||
;;
|
;;
|
||||||
help | -h | --help)
|
help | -h | --help)
|
||||||
show_usage
|
show_usage
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown command: $1"
|
echo "Unknown command: $1"
|
||||||
show_usage
|
show_usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -15,14 +15,14 @@ warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; }
|
|||||||
err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; }
|
err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; }
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
if ! command -v "$1" > /dev/null 2>&1; then
|
||||||
err "Missing dependency: $1"
|
err "Missing dependency: $1"
|
||||||
MISSING=1
|
MISSING=1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub
|
${SCRIPT_NAME} — Download and wire up LeechBlockNG from GitHub
|
||||||
|
|
||||||
Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox]
|
Usage: ${SCRIPT_NAME} [--version vX.Y[.Z]] [--force] [--install-firefox]
|
||||||
@ -44,29 +44,29 @@ VERSION=""
|
|||||||
FORCE=0
|
FORCE=0
|
||||||
AUTO_FIREFOX=0
|
AUTO_FIREFOX=0
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--version)
|
--version)
|
||||||
VERSION="$2"
|
VERSION="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--force)
|
--force)
|
||||||
FORCE=1
|
FORCE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--install-firefox)
|
--install-firefox)
|
||||||
AUTO_FIREFOX=1
|
AUTO_FIREFOX=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
err "Unrecognized option: $1"
|
err "Unrecognized option: $1"
|
||||||
usage
|
usage
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
@ -76,45 +76,45 @@ require_cmd tar
|
|||||||
require_cmd find
|
require_cmd find
|
||||||
require_cmd sed
|
require_cmd sed
|
||||||
require_cmd awk
|
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."
|
warn "jq not found — will fall back to a simpler tag detection method."
|
||||||
fi
|
fi
|
||||||
[[ $MISSING -eq 1 ]] && {
|
[[ $MISSING -eq 1 ]] && {
|
||||||
err "Please install missing tools and re-run."
|
err "Please install missing tools and re-run."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
REPO_OWNER="proginosko"
|
REPO_OWNER="proginosko"
|
||||||
REPO_NAME="LeechBlockNG"
|
REPO_NAME="LeechBlockNG"
|
||||||
|
|
||||||
get_latest_tag() {
|
get_latest_tag() {
|
||||||
local 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)
|
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
|
if [[ -n $tag && $tag != "null" ]]; then
|
||||||
echo "$tag"
|
echo "$tag"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Fallback: follow redirect for /releases/latest to extract tag
|
# Fallback: follow redirect for /releases/latest to extract tag
|
||||||
tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true)
|
tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true)
|
||||||
if [[ -n $tag ]]; then
|
if [[ -n $tag ]]; then
|
||||||
echo "$tag"
|
echo "$tag"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ -z $VERSION ]]; then
|
if [[ -z $VERSION ]]; then
|
||||||
info "Resolving latest release tag from GitHub…"
|
info "Resolving latest release tag from GitHub…"
|
||||||
if ! VERSION=$(get_latest_tag); then
|
if ! VERSION=$(get_latest_tag); then
|
||||||
err "Failed to determine latest version tag"
|
err "Failed to determine latest version tag"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! $VERSION =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then
|
if [[ ! $VERSION =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then
|
||||||
warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway."
|
warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION=${VERSION#v} # strip leading v for folder names
|
VERSION=${VERSION#v} # strip leading v for folder names
|
||||||
@ -126,40 +126,40 @@ VERSION_DIR="$INSTALL_ROOT/$VERSION"
|
|||||||
CURRENT_LINK="$INSTALL_ROOT/current"
|
CURRENT_LINK="$INSTALL_ROOT/current"
|
||||||
|
|
||||||
if [[ -d $VERSION_DIR && $FORCE -ne 1 ]]; then
|
if [[ -d $VERSION_DIR && $FORCE -ne 1 ]]; then
|
||||||
info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)."
|
info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)."
|
||||||
else
|
else
|
||||||
info "Downloading LeechBlockNG $TAG source from GitHub…"
|
info "Downloading LeechBlockNG $TAG source from GitHub…"
|
||||||
tmpdir=$(mktemp -d)
|
tmpdir=$(mktemp -d)
|
||||||
trap 'rm -rf "$tmpdir"' EXIT
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
ARCHIVE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.tar.gz"
|
ARCHIVE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.tar.gz"
|
||||||
ARCHIVE_FILE="$tmpdir/${REPO_NAME}-${TAG}.tar.gz"
|
ARCHIVE_FILE="$tmpdir/${REPO_NAME}-${TAG}.tar.gz"
|
||||||
curl -fL --retry 3 -o "$ARCHIVE_FILE" "$ARCHIVE_URL"
|
curl -fL --retry 3 -o "$ARCHIVE_FILE" "$ARCHIVE_URL"
|
||||||
info "Extracting…"
|
info "Extracting…"
|
||||||
mkdir -p "$tmpdir/extract"
|
mkdir -p "$tmpdir/extract"
|
||||||
tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract"
|
tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract"
|
||||||
# The archive usually extracts to REPO_NAME-TAG/ …
|
# The archive usually extracts to REPO_NAME-TAG/ …
|
||||||
src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true)
|
src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true)
|
||||||
[[ -z $src_root ]] && {
|
[[ -z $src_root ]] && {
|
||||||
err "Could not locate extracted source root"
|
err "Could not locate extracted source root"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Find the extension manifest (support a couple of common layouts)
|
# Find the extension manifest (support a couple of common layouts)
|
||||||
manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true)
|
manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true)
|
||||||
if [[ -z $manifest_path ]]; then
|
if [[ -z $manifest_path ]]; then
|
||||||
err "manifest.json not found in the extracted archive. The project layout may have changed."
|
err "manifest.json not found in the extracted archive. The project layout may have changed."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
ext_dir=$(dirname "$manifest_path")
|
ext_dir=$(dirname "$manifest_path")
|
||||||
|
|
||||||
mkdir -p "$INSTALL_ROOT"
|
mkdir -p "$INSTALL_ROOT"
|
||||||
rm -rf "$VERSION_DIR"
|
rm -rf "$VERSION_DIR"
|
||||||
info "Installing to $VERSION_DIR…"
|
info "Installing to $VERSION_DIR…"
|
||||||
mkdir -p "$VERSION_DIR"
|
mkdir -p "$VERSION_DIR"
|
||||||
# Copy the extension directory as-is (avoid bringing tests or build scripts)
|
# 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"
|
ln -sfn "$VERSION_DIR" "$CURRENT_LINK"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
|
EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
|
||||||
@ -167,21 +167,21 @@ EXT_PATH="$CURRENT_LINK" # stable path used by wrappers
|
|||||||
# Detect browsers
|
# Detect browsers
|
||||||
declare -A BROWSERS
|
declare -A BROWSERS
|
||||||
BROWSERS=(
|
BROWSERS=(
|
||||||
[chromium]="Chromium"
|
[chromium]="Chromium"
|
||||||
[google - chrome - stable]="Google Chrome"
|
[google - chrome - stable]="Google Chrome"
|
||||||
[google - chrome]="Google Chrome"
|
[google - chrome]="Google Chrome"
|
||||||
[brave - browser]="Brave"
|
[brave - browser]="Brave"
|
||||||
[vivaldi - stable]="Vivaldi"
|
[vivaldi - stable]="Vivaldi"
|
||||||
[vivaldi]="Vivaldi"
|
[vivaldi]="Vivaldi"
|
||||||
[opera]="Opera"
|
[opera]="Opera"
|
||||||
[thorium - browser]="Thorium"
|
[thorium - browser]="Thorium"
|
||||||
)
|
)
|
||||||
|
|
||||||
declare -A FIREFOXES
|
declare -A FIREFOXES
|
||||||
FIREFOXES=(
|
FIREFOXES=(
|
||||||
[firefox]="Firefox"
|
[firefox]="Firefox"
|
||||||
[firefox - developer - edition]="Firefox Developer Edition"
|
[firefox - developer - edition]="Firefox Developer Edition"
|
||||||
[librewolf]="LibreWolf"
|
[librewolf]="LibreWolf"
|
||||||
)
|
)
|
||||||
|
|
||||||
found_any=0
|
found_any=0
|
||||||
@ -193,36 +193,36 @@ user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
|
|||||||
mkdir -p "$user_apps_dir"
|
mkdir -p "$user_apps_dir"
|
||||||
|
|
||||||
create_wrapper_and_desktop() {
|
create_wrapper_and_desktop() {
|
||||||
local bin="$1"
|
local bin="$1"
|
||||||
shift
|
shift
|
||||||
local pretty="$1"
|
local pretty="$1"
|
||||||
shift
|
shift
|
||||||
local wrapper="$wrap_bin_dir/${bin}-with-leechblock"
|
local wrapper="$wrap_bin_dir/${bin}-with-leechblock"
|
||||||
|
|
||||||
local real_bin
|
local real_bin
|
||||||
real_bin=$(command -v "$bin" || true)
|
real_bin=$(command -v "$bin" || true)
|
||||||
[[ -z $real_bin ]] && return
|
[[ -z $real_bin ]] && return
|
||||||
|
|
||||||
cat >"$wrapper" <<WRAP
|
cat > "$wrapper" << WRAP
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
exec "$real_bin" --load-extension="$EXT_PATH" "$@"
|
exec "$real_bin" --load-extension="$EXT_PATH" "$@"
|
||||||
WRAP
|
WRAP
|
||||||
chmod +x "$wrapper"
|
chmod +x "$wrapper"
|
||||||
|
|
||||||
# Try to reuse icon from an existing desktop file if available
|
# Try to reuse icon from an existing desktop file if available
|
||||||
local sys_desktop existing_icon existing_name categories
|
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
|
if [[ -n $sys_desktop ]]; then
|
||||||
existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true)
|
existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true)
|
||||||
existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true)
|
existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true)
|
||||||
categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true)
|
categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true)
|
||||||
fi
|
fi
|
||||||
[[ -z $existing_icon ]] && existing_icon="$bin"
|
[[ -z $existing_icon ]] && existing_icon="$bin"
|
||||||
[[ -z $existing_name ]] && existing_name="$pretty"
|
[[ -z $existing_name ]] && existing_name="$pretty"
|
||||||
[[ -z $categories ]] && categories="Network;WebBrowser;"
|
[[ -z $categories ]] && categories="Network;WebBrowser;"
|
||||||
|
|
||||||
local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop"
|
local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop"
|
||||||
cat >"$desktop_file" <<DESK
|
cat > "$desktop_file" << DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=${existing_name} (LeechBlock)
|
Name=${existing_name} (LeechBlock)
|
||||||
Exec=${wrapper} %U
|
Exec=${wrapper} %U
|
||||||
@ -233,35 +233,35 @@ Categories=${categories}
|
|||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
DESK
|
DESK
|
||||||
|
|
||||||
info "Created wrapper: $wrapper"
|
info "Created wrapper: $wrapper"
|
||||||
info "Created launcher: $desktop_file"
|
info "Created launcher: $desktop_file"
|
||||||
found_any=1
|
found_any=1
|
||||||
}
|
}
|
||||||
|
|
||||||
info "Detecting installed browsers…"
|
info "Detecting installed browsers…"
|
||||||
for bin in "${!BROWSERS[@]}"; do
|
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]}"
|
create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
ff_found=0
|
ff_found=0
|
||||||
for bin in "${!FIREFOXES[@]}"; do
|
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
|
ff_found=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [[ $found_any -eq 1 ]]; then
|
if [[ $found_any -eq 1 ]]; then
|
||||||
info "Chromium-based integration complete. Launch the browser via its '(LeechBlock)' launcher."
|
info "Chromium-based integration complete. Launch the browser via its '(LeechBlock)' launcher."
|
||||||
warn "Chromium will mark it as a developer extension; this is expected for unpacked installs."
|
warn "Chromium will mark it as a developer extension; this is expected for unpacked installs."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $ff_found -eq 1 ]]; then
|
if [[ $ff_found -eq 1 ]]; then
|
||||||
echo
|
echo
|
||||||
warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing."
|
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:
|
Options:
|
||||||
1) Install from Mozilla Add-ons (recommended):
|
1) Install from Mozilla Add-ons (recommended):
|
||||||
https://addons.mozilla.org/firefox/addon/leechblock-ng/
|
https://addons.mozilla.org/firefox/addon/leechblock-ng/
|
||||||
@ -276,8 +276,8 @@ FF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $found_any -eq 0 && $ff_found -eq 0 ]]; then
|
if [[ $found_any -eq 0 && $ff_found -eq 0 ]]; then
|
||||||
warn "No supported browsers detected. We placed the extension at: $VERSION_DIR"
|
warn "No supported browsers detected. We placed the extension at: $VERSION_DIR"
|
||||||
echo "Supported (auto-wired): ${!BROWSERS[*]}. Detected Firefox variants will show guidance only."
|
echo "Supported (auto-wired): ${!BROWSERS[*]}. Detected Firefox variants will show guidance only."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
@ -285,36 +285,36 @@ info "Done. Version: $VERSION (tag $TAG) installed under $VERSION_DIR"
|
|||||||
|
|
||||||
# If requested, attempt automatic install on Firefox via enterprise policies
|
# If requested, attempt automatic install on Firefox via enterprise policies
|
||||||
if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
|
if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
|
||||||
echo
|
echo
|
||||||
info "Attempting Firefox auto-install via Enterprise Policies (requires sudo)."
|
info "Attempting Firefox auto-install via Enterprise Policies (requires sudo)."
|
||||||
# AMO info
|
# AMO info
|
||||||
ADDON_ID="leechblockng@proginosko.com"
|
ADDON_ID="leechblockng@proginosko.com"
|
||||||
ADDON_AMO_URL="https://addons.mozilla.org/firefox/downloads/latest/leechblock-ng/latest.xpi"
|
ADDON_AMO_URL="https://addons.mozilla.org/firefox/downloads/latest/leechblock-ng/latest.xpi"
|
||||||
|
|
||||||
# Determine policy directories for detected Firefox-like browsers
|
# Determine policy directories for detected Firefox-like browsers
|
||||||
declare -a POLICY_DIRS
|
declare -a POLICY_DIRS
|
||||||
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")
|
POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution")
|
||||||
fi
|
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")
|
POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution")
|
||||||
fi
|
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")
|
POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution")
|
||||||
fi
|
fi
|
||||||
# Generic mozilla path as fallback
|
# Generic mozilla path as fallback
|
||||||
POLICY_DIRS+=("/usr/lib/mozilla/distribution")
|
POLICY_DIRS+=("/usr/lib/mozilla/distribution")
|
||||||
|
|
||||||
updated_any=0
|
updated_any=0
|
||||||
for pol_target in "${POLICY_DIRS[@]}"; do
|
for pol_target in "${POLICY_DIRS[@]}"; do
|
||||||
tmp_pol=$(mktemp)
|
tmp_pol=$(mktemp)
|
||||||
existing="${pol_target}/policies.json"
|
existing="${pol_target}/policies.json"
|
||||||
if sudo test -f "$existing"; then
|
if sudo test -f "$existing"; then
|
||||||
info "Merging into existing policies.json at $existing"
|
info "Merging into existing policies.json at $existing"
|
||||||
sudo cp "$existing" "$tmp_pol"
|
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" '
|
merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" '
|
||||||
.policies |= (. // {}) |
|
.policies |= (. // {}) |
|
||||||
.policies.ExtensionSettings |= (. // {}) |
|
.policies.ExtensionSettings |= (. // {}) |
|
||||||
.policies.ExtensionSettings."*" |= (. // {"installation_mode":"allowed"}) |
|
.policies.ExtensionSettings."*" |= (. // {"installation_mode":"allowed"}) |
|
||||||
@ -322,17 +322,17 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
|
|||||||
.policies.ExtensionSettings[$id].installation_mode = "force_installed" |
|
.policies.ExtensionSettings[$id].installation_mode = "force_installed" |
|
||||||
.policies.ExtensionSettings[$id].install_url = $url
|
.policies.ExtensionSettings[$id].install_url = $url
|
||||||
' "$tmp_pol") || merged=""
|
' "$tmp_pol") || merged=""
|
||||||
if [[ -n $merged ]]; then
|
if [[ -n $merged ]]; then
|
||||||
printf '%s\n' "$merged" >"$tmp_pol"
|
printf '%s\n' "$merged" > "$tmp_pol"
|
||||||
else
|
else
|
||||||
warn "jq merge failed; skipping $pol_target"
|
warn "jq merge failed; skipping $pol_target"
|
||||||
rm -f "$tmp_pol"
|
rm -f "$tmp_pol"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
warn "jq not available; creating minimal policies.json (existing file will be backed up)."
|
warn "jq not available; creating minimal policies.json (existing file will be backed up)."
|
||||||
sudo cp "$existing" "${existing}.bak.$(date +%s)"
|
sudo cp "$existing" "${existing}.bak.$(date +%s)"
|
||||||
cat >"$tmp_pol" <<JSON
|
cat > "$tmp_pol" << JSON
|
||||||
{
|
{
|
||||||
"policies": {
|
"policies": {
|
||||||
"ExtensionSettings": {
|
"ExtensionSettings": {
|
||||||
@ -345,10 +345,10 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
info "Creating new policies.json at $pol_target"
|
info "Creating new policies.json at $pol_target"
|
||||||
cat >"$tmp_pol" <<JSON
|
cat > "$tmp_pol" << JSON
|
||||||
{
|
{
|
||||||
"policies": {
|
"policies": {
|
||||||
"ExtensionSettings": {
|
"ExtensionSettings": {
|
||||||
@ -361,18 +361,18 @@ JSON
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo mkdir -p "$pol_target"
|
sudo mkdir -p "$pol_target"
|
||||||
sudo cp "$tmp_pol" "$pol_target/policies.json"
|
sudo cp "$tmp_pol" "$pol_target/policies.json"
|
||||||
rm -f "$tmp_pol"
|
rm -f "$tmp_pol"
|
||||||
updated_any=1
|
updated_any=1
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $updated_any -eq 1 ]]; then
|
if [[ $updated_any -eq 1 ]]; then
|
||||||
info "Firefox policies updated. Restart Firefox/LibreWolf to complete installation of LeechBlock NG."
|
info "Firefox policies updated. Restart Firefox/LibreWolf to complete installation of LeechBlock NG."
|
||||||
else
|
else
|
||||||
warn "No Firefox policy locations updated. You may not have a supported Firefox installed."
|
warn "No Firefox policy locations updated. You may not have a supported Firefox installed."
|
||||||
fi
|
fi
|
||||||
info "Firefox policy updated. Restart Firefox to complete installation of LeechBlock NG."
|
info "Firefox policy updated. Restart Firefox to complete installation of LeechBlock NG."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -16,333 +16,333 @@ source "$SCRIPT_DIR/../lib/common.sh"
|
|||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism"
|
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"
|
export LOG_FILE="$LOG_DIR/music-parallelism.log"
|
||||||
CHECK_INTERVAL=3
|
CHECK_INTERVAL=3
|
||||||
|
|
||||||
# Override focus apps with extended list for this script
|
# Override focus apps with extended list for this script
|
||||||
FOCUS_APPS_WINDOWS=(
|
FOCUS_APPS_WINDOWS=(
|
||||||
# IDEs and code editors - match window titles
|
# IDEs and code editors - match window titles
|
||||||
"Visual Studio Code"
|
"Visual Studio Code"
|
||||||
"VSCodium"
|
"VSCodium"
|
||||||
"Cursor"
|
"Cursor"
|
||||||
"IntelliJ IDEA"
|
"IntelliJ IDEA"
|
||||||
"PyCharm"
|
"PyCharm"
|
||||||
"WebStorm"
|
"WebStorm"
|
||||||
"CLion"
|
"CLion"
|
||||||
"Rider"
|
"Rider"
|
||||||
"Sublime Text"
|
"Sublime Text"
|
||||||
"Atom"
|
"Atom"
|
||||||
"Neovide"
|
"Neovide"
|
||||||
# Gaming
|
# Gaming
|
||||||
"Steam"
|
"Steam"
|
||||||
# Creative apps
|
# Creative apps
|
||||||
"Blender"
|
"Blender"
|
||||||
"Godot"
|
"Godot"
|
||||||
"Unity"
|
"Unity"
|
||||||
"Unreal Editor"
|
"Unreal Editor"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Music streaming services - browser tabs or electron apps
|
# Music streaming services - browser tabs or electron apps
|
||||||
# These will be killed when focus apps are detected
|
# These will be killed when focus apps are detected
|
||||||
MUSIC_SERVICES=(
|
MUSIC_SERVICES=(
|
||||||
# YouTube Music specific patterns (NOT regular YouTube)
|
# YouTube Music specific patterns (NOT regular YouTube)
|
||||||
"music.youtube.com"
|
"music.youtube.com"
|
||||||
"youtube-music" # Electron app
|
"youtube-music" # Electron app
|
||||||
"YouTube Music" # Window title
|
"YouTube Music" # Window title
|
||||||
# Spotify
|
# Spotify
|
||||||
"spotify"
|
"spotify"
|
||||||
"Spotify"
|
"Spotify"
|
||||||
# Tidal
|
# Tidal
|
||||||
"tidal"
|
"tidal"
|
||||||
"TIDAL"
|
"TIDAL"
|
||||||
# Deezer
|
# Deezer
|
||||||
"deezer"
|
"deezer"
|
||||||
# Amazon Music
|
# Amazon Music
|
||||||
"Amazon Music"
|
"Amazon Music"
|
||||||
"amazon music"
|
"amazon music"
|
||||||
# Apple Music (web)
|
# Apple Music (web)
|
||||||
"music.apple.com"
|
"music.apple.com"
|
||||||
# SoundCloud
|
# SoundCloud
|
||||||
"soundcloud.com"
|
"soundcloud.com"
|
||||||
# Pandora
|
# Pandora
|
||||||
"pandora.com"
|
"pandora.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if any music service is running and return its details
|
# Check if any music service is running and return its details
|
||||||
find_music_services() {
|
find_music_services() {
|
||||||
local found_services=()
|
local found_services=()
|
||||||
|
|
||||||
for service in "${MUSIC_SERVICES[@]}"; do
|
for service in "${MUSIC_SERVICES[@]}"; do
|
||||||
# Check for browser tabs with music services
|
# Check for browser tabs with music services
|
||||||
# This checks window titles which usually contain the URL or tab title
|
# This checks window titles which usually contain the URL or tab title
|
||||||
if command -v xdotool &>/dev/null; then
|
if command -v xdotool &> /dev/null; then
|
||||||
if xdotool search --name "$service" &>/dev/null 2>&1; then
|
if xdotool search --name "$service" &> /dev/null 2>&1; then
|
||||||
found_services+=("$service (window)")
|
found_services+=("$service (window)")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for dedicated desktop apps
|
# 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)")
|
found_services+=("$service (process)")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#found_services[@]} -gt 0 ]]; then
|
if [[ ${#found_services[@]} -gt 0 ]]; then
|
||||||
printf '%s\n' "${found_services[@]}"
|
printf '%s\n' "${found_services[@]}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Kill music services
|
# Kill music services
|
||||||
kill_music_services() {
|
kill_music_services() {
|
||||||
local killed=false
|
local killed=false
|
||||||
|
|
||||||
# Kill YouTube Music browser tabs
|
# Kill YouTube Music browser tabs
|
||||||
# YouTube Music runs in browser, so we need to close specific 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"
|
# 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
|
# Find windows with YouTube Music in title
|
||||||
local yt_music_windows
|
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
|
for wid in $yt_music_windows; do
|
||||||
if [[ -n $wid ]]; then
|
if [[ -n $wid ]]; then
|
||||||
# Get window name for logging
|
# Get window name for logging
|
||||||
local wname
|
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
|
# Only close if it's YouTube Music, not regular YouTube
|
||||||
if [[ $wname == *"YouTube Music"* ]] || [[ $wname == *"music.youtube.com"* ]]; then
|
if [[ $wname == *"YouTube Music"* ]] || [[ $wname == *"music.youtube.com"* ]]; then
|
||||||
log_message "Closing YouTube Music window: $wname (ID: $wid)"
|
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
|
killed=true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kill YouTube Music Electron app
|
# 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"
|
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
|
killed=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kill Spotify
|
# Kill Spotify
|
||||||
if pgrep -x "spotify" &>/dev/null; then
|
if pgrep -x "spotify" &> /dev/null; then
|
||||||
log_message "Killing Spotify"
|
log_message "Killing Spotify"
|
||||||
pkill -9 -x "spotify" 2>/dev/null || true
|
pkill -9 -x "spotify" 2> /dev/null || true
|
||||||
killed=true
|
killed=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Kill other music streaming app processes
|
# Kill other music streaming app processes
|
||||||
local music_processes=("tidal" "deezer" "Amazon Music")
|
local music_processes=("tidal" "deezer" "Amazon Music")
|
||||||
for proc in "${music_processes[@]}"; do
|
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"
|
log_message "Killing $proc"
|
||||||
pkill -9 -i -f "$proc" 2>/dev/null || true
|
pkill -9 -i -f "$proc" 2> /dev/null || true
|
||||||
killed=true
|
killed=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Close browser tabs for web-based music services
|
# 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")
|
local web_music_patterns=("music.apple.com" "soundcloud.com" "pandora.com" "deezer.com" "tidal.com")
|
||||||
for pattern in "${web_music_patterns[@]}"; do
|
for pattern in "${web_music_patterns[@]}"; do
|
||||||
local windows
|
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
|
for wid in $windows; do
|
||||||
if [[ -n $wid ]]; then
|
if [[ -n $wid ]]; then
|
||||||
local wname
|
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)"
|
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
|
killed=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if $killed; then
|
if $killed; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Send notification to user
|
# Send notification to user
|
||||||
notify_user() {
|
notify_user() {
|
||||||
local focus_app="$1"
|
local focus_app="$1"
|
||||||
local message="Music stopped - focus mode active ($focus_app detected)"
|
local message="Music stopped - focus mode active ($focus_app detected)"
|
||||||
|
|
||||||
# Try to send desktop notification
|
# Try to send desktop notification
|
||||||
if command -v notify-send &>/dev/null; then
|
if command -v notify-send &> /dev/null; then
|
||||||
notify-send -u normal -t 5000 "🎵 Music Parallelism" "$message" 2>/dev/null || true
|
notify-send -u normal -t 5000 "🎵 Music Parallelism" "$message" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_message "$message"
|
log_message "$message"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Instant monitoring loop - uses polling at high frequency
|
# Instant monitoring loop - uses polling at high frequency
|
||||||
# This runs every 0.5 seconds for near-instant detection
|
# This runs every 0.5 seconds for near-instant detection
|
||||||
instant_monitor_loop() {
|
instant_monitor_loop() {
|
||||||
log_message "=== Music Parallelism INSTANT Monitor Started ==="
|
log_message "=== Music Parallelism INSTANT Monitor Started ==="
|
||||||
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
||||||
log_message "Focus apps (processes): ${FOCUS_APPS_PROCESSES[*]}"
|
log_message "Focus apps (processes): ${FOCUS_APPS_PROCESSES[*]}"
|
||||||
log_message "Polling every 0.5 seconds for instant kill"
|
log_message "Polling every 0.5 seconds for instant kill"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Only check if focus app is running
|
# 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
|
# Instant kill youtube-music if detected
|
||||||
if pgrep -f "youtube-music" &>/dev/null; then
|
if pgrep -f "youtube-music" &> /dev/null; then
|
||||||
pkill -9 -f "youtube-music" 2>/dev/null || true
|
pkill -9 -f "youtube-music" 2> /dev/null || true
|
||||||
log_message "INSTANT KILL: YouTube Music terminated"
|
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
|
fi
|
||||||
# Also check other music services
|
# Also check other music services
|
||||||
if pgrep -x "spotify" &>/dev/null; then
|
if pgrep -x "spotify" &> /dev/null; then
|
||||||
pkill -9 -x "spotify" 2>/dev/null || true
|
pkill -9 -x "spotify" 2> /dev/null || true
|
||||||
log_message "INSTANT KILL: Spotify terminated"
|
log_message "INSTANT KILL: Spotify terminated"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main monitoring loop
|
# Main monitoring loop
|
||||||
monitor_loop() {
|
monitor_loop() {
|
||||||
log_message "=== Music Parallelism Monitor Started ==="
|
log_message "=== Music Parallelism Monitor Started ==="
|
||||||
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
||||||
log_message "Focus apps (processes): ${FOCUS_APPS_PROCESSES[*]}"
|
log_message "Focus apps (processes): ${FOCUS_APPS_PROCESSES[*]}"
|
||||||
log_message "Music services monitored: ${MUSIC_SERVICES[*]}"
|
log_message "Music services monitored: ${MUSIC_SERVICES[*]}"
|
||||||
log_message "Check interval: ${CHECK_INTERVAL}s"
|
log_message "Check interval: ${CHECK_INTERVAL}s"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Check if a focus app is running
|
# Check if a focus app is running
|
||||||
local focus_app
|
local focus_app
|
||||||
if focus_app=$(is_focus_app_running); then
|
if focus_app=$(is_focus_app_running); then
|
||||||
# Focus app detected, check for music services
|
# Focus app detected, check for music services
|
||||||
local music_services
|
local music_services
|
||||||
if music_services=$(find_music_services); then
|
if music_services=$(find_music_services); then
|
||||||
log_message "Conflict detected: Focus app '$focus_app' running with music services"
|
log_message "Conflict detected: Focus app '$focus_app' running with music services"
|
||||||
log_message "Active music services: $music_services"
|
log_message "Active music services: $music_services"
|
||||||
|
|
||||||
# Kill the music services
|
# Kill the music services
|
||||||
if kill_music_services; then
|
if kill_music_services; then
|
||||||
notify_user "$focus_app"
|
notify_user "$focus_app"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sleep "$CHECK_INTERVAL"
|
sleep "$CHECK_INTERVAL"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show status
|
# Show status
|
||||||
show_status() {
|
show_status() {
|
||||||
echo "Music Parallelism Monitor Status"
|
echo "Music Parallelism Monitor Status"
|
||||||
echo "================================="
|
echo "================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo "Focus Applications (window-based detection):"
|
echo "Focus Applications (window-based detection):"
|
||||||
local focus_running=false
|
local focus_running=false
|
||||||
|
|
||||||
# Check windows
|
# Check windows
|
||||||
if command -v xdotool &>/dev/null; then
|
if command -v xdotool &> /dev/null; then
|
||||||
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
|
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)"
|
echo " ✓ $app (WINDOW OPEN)"
|
||||||
focus_running=true
|
focus_running=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check processes
|
# Check processes
|
||||||
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
|
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)"
|
echo " ✓ $app (PROCESS RUNNING)"
|
||||||
focus_running=true
|
focus_running=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! $focus_running; then
|
if ! $focus_running; then
|
||||||
echo " (none detected)"
|
echo " (none detected)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Music Services:"
|
echo "Music Services:"
|
||||||
local music_running=false
|
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 "$music_services" | while read -r svc; do
|
||||||
echo " ♪ $svc (RUNNING)"
|
echo " ♪ $svc (RUNNING)"
|
||||||
done
|
done
|
||||||
music_running=true
|
music_running=true
|
||||||
fi
|
fi
|
||||||
if ! $music_running; then
|
if ! $music_running; then
|
||||||
echo " (none detected)"
|
echo " (none detected)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if $focus_running && $music_running; then
|
if $focus_running && $music_running; then
|
||||||
echo "⚠️ CONFLICT: Focus app and music running together!"
|
echo "⚠️ CONFLICT: Focus app and music running together!"
|
||||||
echo " Music would be killed in monitoring mode."
|
echo " Music would be killed in monitoring mode."
|
||||||
elif $focus_running; then
|
elif $focus_running; then
|
||||||
echo "✓ Focus mode active (no music playing)"
|
echo "✓ Focus mode active (no music playing)"
|
||||||
elif $music_running; then
|
elif $music_running; then
|
||||||
echo "✓ Music playing (no focus app detected - this is fine)"
|
echo "✓ Music playing (no focus app detected - this is fine)"
|
||||||
else
|
else
|
||||||
echo "✓ Idle (nothing detected)"
|
echo "✓ Idle (nothing detected)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show usage
|
# Show usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Music Parallelism Prevention Script"
|
echo "Music Parallelism Prevention Script"
|
||||||
echo "===================================="
|
echo "===================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage: $0 [command]"
|
echo "Usage: $0 [command]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Commands:"
|
echo "Commands:"
|
||||||
echo " monitor - Start monitoring (default, checks every ${CHECK_INTERVAL}s)"
|
echo " monitor - Start monitoring (default, checks every ${CHECK_INTERVAL}s)"
|
||||||
echo " instant - Instant monitoring (checks every 0.5s for immediate kill)"
|
echo " instant - Instant monitoring (checks every 0.5s for immediate kill)"
|
||||||
echo " status - Show current status of focus apps and music services"
|
echo " status - Show current status of focus apps and music services"
|
||||||
echo " kill - Immediately kill all music services"
|
echo " kill - Immediately kill all music services"
|
||||||
echo " help - Show this help message"
|
echo " help - Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Description:"
|
echo "Description:"
|
||||||
echo " This script prevents multitasking between focus work and music."
|
echo " This script prevents multitasking between focus work and music."
|
||||||
echo " When a focus application (VS Code, Steam, etc.) is detected"
|
echo " When a focus application (VS Code, Steam, etc.) is detected"
|
||||||
echo " alongside a music streaming service, the music is stopped."
|
echo " alongside a music streaming service, the music is stopped."
|
||||||
echo ""
|
echo ""
|
||||||
echo " Music is allowed when no focus apps are running."
|
echo " Music is allowed when no focus apps are running."
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
case "${1:-instant}" in
|
case "${1:-instant}" in
|
||||||
monitor | start | run)
|
monitor | start | run)
|
||||||
monitor_loop
|
monitor_loop
|
||||||
;;
|
;;
|
||||||
instant | fast)
|
instant | fast)
|
||||||
instant_monitor_loop
|
instant_monitor_loop
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
show_status
|
show_status
|
||||||
;;
|
;;
|
||||||
kill)
|
kill)
|
||||||
log_message "Manual kill requested"
|
log_message "Manual kill requested"
|
||||||
if kill_music_services; then
|
if kill_music_services; then
|
||||||
echo "Music services killed"
|
echo "Music services killed"
|
||||||
else
|
else
|
||||||
echo "No music services found to kill"
|
echo "No music services found to kill"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
help | -h | --help)
|
help | -h | --help)
|
||||||
show_usage
|
show_usage
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown command: $1"
|
echo "Unknown command: $1"
|
||||||
show_usage
|
show_usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
# Auto-sudo functionality
|
# Auto-sudo functionality
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo "Executing with sudo..."
|
echo "Executing with sudo..."
|
||||||
sudo "$0" "$@"
|
sudo "$0" "$@"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
@ -30,14 +30,14 @@ WHITELIST_DEST="${INSTALL_DIR}/pacman_whitelist.txt"
|
|||||||
GREYLIST_DEST="${INSTALL_DIR}/pacman_greylist.txt"
|
GREYLIST_DEST="${INSTALL_DIR}/pacman_greylist.txt"
|
||||||
# Check if script is run as root
|
# Check if script is run as root
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
echo -e "${RED}Please run as root${NC}"
|
echo -e "${RED}Please run as root${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if the wrapper script exists
|
# Check if the wrapper script exists
|
||||||
if [ ! -f "$WRAPPER_SOURCE" ]; then
|
if [ ! -f "$WRAPPER_SOURCE" ]; then
|
||||||
echo -e "${RED}Error: Wrapper script not found at ${WRAPPER_SOURCE}${NC}"
|
echo -e "${RED}Error: Wrapper script not found at ${WRAPPER_SOURCE}${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${CYAN}Installing pacman wrapper...${NC}"
|
echo -e "${CYAN}Installing pacman wrapper...${NC}"
|
||||||
@ -47,32 +47,32 @@ echo -e "${BLUE}Copying wrapper script to ${WRAPPER_DEST}...${NC}"
|
|||||||
cp "$WRAPPER_SOURCE" "$WRAPPER_DEST"
|
cp "$WRAPPER_SOURCE" "$WRAPPER_DEST"
|
||||||
cp "$WORDS_SOURCE" "$WORDS_DEST"
|
cp "$WORDS_SOURCE" "$WORDS_DEST"
|
||||||
if [ -f "$BLOCKED_SOURCE" ]; then
|
if [ -f "$BLOCKED_SOURCE" ]; then
|
||||||
cp "$BLOCKED_SOURCE" "$BLOCKED_DEST"
|
cp "$BLOCKED_SOURCE" "$BLOCKED_DEST"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Warning:${NC} Missing blocked keywords source at ${BLOCKED_SOURCE}${NC}"
|
echo -e "${YELLOW}Warning:${NC} Missing blocked keywords source at ${BLOCKED_SOURCE}${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$WHITELIST_SOURCE" ]; then
|
if [ -f "$WHITELIST_SOURCE" ]; then
|
||||||
cp "$WHITELIST_SOURCE" "$WHITELIST_DEST"
|
cp "$WHITELIST_SOURCE" "$WHITELIST_DEST"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}"
|
echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$GREYLIST_SOURCE" ]; then
|
if [ -f "$GREYLIST_SOURCE" ]; then
|
||||||
cp "$GREYLIST_SOURCE" "$GREYLIST_DEST"
|
cp "$GREYLIST_SOURCE" "$GREYLIST_DEST"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Warning:${NC} Missing greylist source at ${GREYLIST_SOURCE}${NC}"
|
echo -e "${YELLOW}Warning:${NC} Missing greylist source at ${GREYLIST_SOURCE}${NC}"
|
||||||
fi
|
fi
|
||||||
chmod +x "$WRAPPER_DEST"
|
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
|
# Automatically use symbolic link installation method
|
||||||
echo -e "${YELLOW}Installing using symbolic link method...${NC}"
|
echo -e "${YELLOW}Installing using symbolic link method...${NC}"
|
||||||
|
|
||||||
# Backup original pacman
|
# Backup original pacman
|
||||||
if [ ! -f "/usr/bin/pacman.orig" ]; then
|
if [ ! -f "/usr/bin/pacman.orig" ]; then
|
||||||
echo -e "${BLUE}Backing up original pacman to /usr/bin/pacman.orig...${NC}"
|
echo -e "${BLUE}Backing up original pacman to /usr/bin/pacman.orig...${NC}"
|
||||||
cp /usr/bin/pacman /usr/bin/pacman.orig
|
cp /usr/bin/pacman /usr/bin/pacman.orig
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update the PACMAN_BIN variable in the wrapper to point to the original
|
# Update the PACMAN_BIN variable in the wrapper to point to the original
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -19,9 +19,9 @@ echo "======================================"
|
|||||||
echo "Current Date: $(date)"
|
echo "Current Date: $(date)"
|
||||||
echo "User: $(get_actual_user)"
|
echo "User: $(get_actual_user)"
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
echo "Mode: Interactive (prompts enabled)"
|
echo "Mode: Interactive (prompts enabled)"
|
||||||
else
|
else
|
||||||
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get the actual user (even when running with sudo)
|
# Get the actual user (even when running with sudo)
|
||||||
@ -33,147 +33,147 @@ echo "User home: $USER_HOME"
|
|||||||
|
|
||||||
# Function to check if today is a monitored day
|
# Function to check if today is a monitored day
|
||||||
is_monitored_day() {
|
is_monitored_day() {
|
||||||
local day_of_week
|
local day_of_week
|
||||||
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
||||||
|
|
||||||
# Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7)
|
# Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7)
|
||||||
if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then
|
if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then
|
||||||
return 0 # Yes, it's a monitored day
|
return 0 # Yes, it's a monitored day
|
||||||
else
|
else
|
||||||
return 1 # No, it's not a monitored day
|
return 1 # No, it's not a monitored day
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if current time is between 5AM and 8AM
|
# Function to check if current time is between 5AM and 8AM
|
||||||
is_current_time_in_window() {
|
is_current_time_in_window() {
|
||||||
local current_hour current_hour_num
|
local current_hour current_hour_num
|
||||||
current_hour=$(date +%H)
|
current_hour=$(date +%H)
|
||||||
current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues
|
current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues
|
||||||
|
|
||||||
if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
|
if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
|
||||||
return 0 # Yes, current time is in the 5AM-8AM window
|
return 0 # Yes, current time is in the 5AM-8AM window
|
||||||
else
|
else
|
||||||
return 1 # No, current time is outside the window
|
return 1 # No, current time is outside the window
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if PC was booted between 5AM-8AM today
|
# Function to check if PC was booted between 5AM-8AM today
|
||||||
was_booted_in_window_today() {
|
was_booted_in_window_today() {
|
||||||
local today boot_time
|
local today boot_time
|
||||||
today=$(date +%Y-%m-%d)
|
today=$(date +%Y-%m-%d)
|
||||||
boot_time=""
|
boot_time=""
|
||||||
|
|
||||||
# Get the last boot time using multiple methods for reliability
|
# 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
|
# Method 1: Calculate boot time from uptime
|
||||||
local uptime_seconds
|
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
|
if [[ $uptime_seconds -gt 0 ]]; then
|
||||||
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
|
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Method 2: Use systemd if available (fallback)
|
# Method 2: Use systemd if available (fallback)
|
||||||
if [[ -z $boot_time ]] && command -v systemctl &>/dev/null; then
|
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 "")
|
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2> /dev/null || echo "")
|
||||||
if [[ -n $boot_time ]]; then
|
if [[ -n $boot_time ]]; then
|
||||||
# This gives us relative time, need to calculate absolute time
|
# This gives us relative time, need to calculate absolute time
|
||||||
local current_time uptime_sec
|
local current_time uptime_sec
|
||||||
current_time=$(date +%s)
|
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")
|
boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Method 3: Use who -b (fallback)
|
# Method 3: Use who -b (fallback)
|
||||||
if [[ -z $boot_time ]] && command -v who &>/dev/null; then
|
if [[ -z $boot_time ]] && command -v who &> /dev/null; then
|
||||||
boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "")
|
boot_time=$(who -b | awk '{print $3, $4}' 2> /dev/null || echo "")
|
||||||
if [[ -n $boot_time ]]; then
|
if [[ -n $boot_time ]]; then
|
||||||
boot_time="$today $boot_time"
|
boot_time="$today $boot_time"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Method 4: Use /proc/uptime as final fallback
|
# Method 4: Use /proc/uptime as final fallback
|
||||||
if [[ -z $boot_time ]]; then
|
if [[ -z $boot_time ]]; then
|
||||||
local uptime_seconds
|
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")
|
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Boot time detected: $boot_time"
|
echo "Boot time detected: $boot_time"
|
||||||
|
|
||||||
# Check if boot time is from today
|
# Check if boot time is from today
|
||||||
local boot_date
|
local boot_date
|
||||||
boot_date=$(echo "$boot_time" | cut -d' ' -f1)
|
boot_date=$(echo "$boot_time" | cut -d' ' -f1)
|
||||||
if [[ $boot_date != "$today" ]]; then
|
if [[ $boot_date != "$today" ]]; then
|
||||||
echo "PC was not booted today (boot date: $boot_date, today: $today)"
|
echo "PC was not booted today (boot date: $boot_date, today: $today)"
|
||||||
return 1 # Not booted today
|
return 1 # Not booted today
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract hour from boot time
|
# Extract hour from boot time
|
||||||
local boot_hour boot_hour_num
|
local boot_hour boot_hour_num
|
||||||
boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1)
|
boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1)
|
||||||
boot_hour_num=$((10#$boot_hour)) # Convert to decimal
|
boot_hour_num=$((10#$boot_hour)) # Convert to decimal
|
||||||
|
|
||||||
echo "Boot hour: $boot_hour_num"
|
echo "Boot hour: $boot_hour_num"
|
||||||
|
|
||||||
# Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM)
|
# Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM)
|
||||||
if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then
|
if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then
|
||||||
echo "PC was booted in the expected window (5AM-8AM)"
|
echo "PC was booted in the expected window (5AM-8AM)"
|
||||||
return 0 # Yes, booted in window
|
return 0 # Yes, booted in window
|
||||||
else
|
else
|
||||||
echo "PC was NOT booted in the expected window (5AM-8AM)"
|
echo "PC was NOT booted in the expected window (5AM-8AM)"
|
||||||
return 1 # No, not booted in window
|
return 1 # No, not booted in window
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show notification/warning
|
# Function to show notification/warning
|
||||||
show_startup_warning() {
|
show_startup_warning() {
|
||||||
local day_name current_time today
|
local day_name current_time today
|
||||||
day_name=$(date +%A)
|
day_name=$(date +%A)
|
||||||
current_time=$(date +"%H:%M")
|
current_time=$(date +"%H:%M")
|
||||||
today=$(date +%Y-%m-%d)
|
today=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ PC STARTUP TIME WARNING"
|
echo "⚠️ PC STARTUP TIME WARNING"
|
||||||
echo "=========================="
|
echo "=========================="
|
||||||
echo "Date: $today ($day_name)"
|
echo "Date: $today ($day_name)"
|
||||||
echo "Current time: $current_time"
|
echo "Current time: $current_time"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today,"
|
echo "This PC was expected to be turned on between 5:00 AM and 8:00 AM today,"
|
||||||
echo "but it was not turned on during that time window."
|
echo "but it was not turned on during that time window."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM"
|
echo "Expected: Monday, Friday, Saturday, Sunday between 5:00-8:00 AM"
|
||||||
echo "Actual: PC was turned on outside the expected window"
|
echo "Actual: PC was turned on outside the expected window"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Log the warning
|
# Log the warning
|
||||||
logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today"
|
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
|
# 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
|
if [[ $EUID -eq 0 ]]; then
|
||||||
# Running as root, send notification as user
|
# 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
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "This warning has been logged to the system journal."
|
echo "This warning has been logged to the system journal."
|
||||||
echo "You can view startup logs with: journalctl -t pc-startup-monitor"
|
echo "You can view startup logs with: journalctl -t pc-startup-monitor"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the monitoring service
|
# Function to create the monitoring service
|
||||||
create_monitoring_service() {
|
create_monitoring_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Creating PC Startup Monitor Service..."
|
echo "1. Creating PC Startup Monitor Service..."
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
|
|
||||||
local service_file="/etc/systemd/system/pc-startup-monitor.service"
|
local service_file="/etc/systemd/system/pc-startup-monitor.service"
|
||||||
|
|
||||||
cat >"$service_file" <<'EOF'
|
cat > "$service_file" << 'EOF'
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=PC Startup Time Monitor
|
Description=PC Startup Time Monitor
|
||||||
After=multi-user.target
|
After=multi-user.target
|
||||||
@ -190,18 +190,18 @@ RemainAfterExit=true
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created monitoring service: $service_file"
|
echo "✓ Created monitoring service: $service_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the monitoring timer
|
# Function to create the monitoring timer
|
||||||
create_monitoring_timer() {
|
create_monitoring_timer() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Creating PC Startup Monitor Timer..."
|
echo "2. Creating PC Startup Monitor Timer..."
|
||||||
echo "====================================="
|
echo "====================================="
|
||||||
|
|
||||||
local timer_file="/etc/systemd/system/pc-startup-monitor.timer"
|
local timer_file="/etc/systemd/system/pc-startup-monitor.timer"
|
||||||
|
|
||||||
cat >"$timer_file" <<'EOF'
|
cat > "$timer_file" << 'EOF'
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Timer for PC startup monitoring
|
Description=Timer for PC startup monitoring
|
||||||
Requires=pc-startup-monitor.service
|
Requires=pc-startup-monitor.service
|
||||||
@ -215,18 +215,18 @@ AccuracySec=1m
|
|||||||
WantedBy=timers.target
|
WantedBy=timers.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created monitoring timer: $timer_file"
|
echo "✓ Created monitoring timer: $timer_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the main monitoring script
|
# Function to create the main monitoring script
|
||||||
create_monitoring_script() {
|
create_monitoring_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Creating PC Startup Monitor Script..."
|
echo "3. Creating PC Startup Monitor Script..."
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
local script_file="/usr/local/bin/pc-startup-check.sh"
|
local script_file="/usr/local/bin/pc-startup-check.sh"
|
||||||
|
|
||||||
cat >"$script_file" <<'EOF'
|
cat > "$script_file" << 'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# PC Startup Time Monitor Check Script
|
# PC Startup Time Monitor Check Script
|
||||||
# Monitors if PC was turned on during expected hours on specific days
|
# Monitors if PC was turned on during expected hours on specific days
|
||||||
@ -332,19 +332,19 @@ else
|
|||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$script_file"
|
chmod +x "$script_file"
|
||||||
echo "✓ Created monitoring script: $script_file"
|
echo "✓ Created monitoring script: $script_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create management script
|
# Function to create management script
|
||||||
create_management_script() {
|
create_management_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Creating Management Script..."
|
echo "4. Creating Management Script..."
|
||||||
echo "=============================="
|
echo "=============================="
|
||||||
|
|
||||||
local script_file="/usr/local/bin/pc-startup-monitor-manager.sh"
|
local script_file="/usr/local/bin/pc-startup-monitor-manager.sh"
|
||||||
|
|
||||||
cat >"$script_file" <<'EOF'
|
cat > "$script_file" << 'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# PC Startup Monitor Manager
|
# PC Startup Monitor Manager
|
||||||
# Provides easy management of the PC startup monitoring feature
|
# Provides easy management of the PC startup monitoring feature
|
||||||
@ -407,150 +407,150 @@ case "$1" in
|
|||||||
esac
|
esac
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$script_file"
|
chmod +x "$script_file"
|
||||||
echo "✓ Created management script: $script_file"
|
echo "✓ Created management script: $script_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to enable the services
|
# Function to enable the services
|
||||||
enable_services() {
|
enable_services() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "5. Enabling PC Startup Monitor..."
|
echo "5. Enabling PC Startup Monitor..."
|
||||||
echo "==============================="
|
echo "==============================="
|
||||||
|
|
||||||
# Reload systemd daemon
|
# Reload systemd daemon
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
echo "✓ Reloaded systemd daemon"
|
echo "✓ Reloaded systemd daemon"
|
||||||
|
|
||||||
# Enable and start the timer
|
# Enable and start the timer
|
||||||
systemctl enable pc-startup-monitor.timer
|
systemctl enable pc-startup-monitor.timer
|
||||||
echo "✓ Enabled pc-startup-monitor timer"
|
echo "✓ Enabled pc-startup-monitor timer"
|
||||||
|
|
||||||
systemctl start pc-startup-monitor.timer
|
systemctl start pc-startup-monitor.timer
|
||||||
echo "✓ Started pc-startup-monitor timer"
|
echo "✓ Started pc-startup-monitor timer"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to test the setup
|
# Function to test the setup
|
||||||
test_setup() {
|
test_setup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Testing Setup..."
|
echo "6. Testing Setup..."
|
||||||
echo "=================="
|
echo "=================="
|
||||||
|
|
||||||
echo "Service files:"
|
echo "Service files:"
|
||||||
if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then
|
if [[ -f "/etc/systemd/system/pc-startup-monitor.service" ]]; then
|
||||||
echo "✓ Service file exists"
|
echo "✓ Service file exists"
|
||||||
else
|
else
|
||||||
echo "✗ Service file missing"
|
echo "✗ Service file missing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then
|
if [[ -f "/etc/systemd/system/pc-startup-monitor.timer" ]]; then
|
||||||
echo "✓ Timer file exists"
|
echo "✓ Timer file exists"
|
||||||
else
|
else
|
||||||
echo "✗ Timer file missing"
|
echo "✗ Timer file missing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Timer status:"
|
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"
|
echo "✓ Timer is enabled"
|
||||||
else
|
else
|
||||||
echo "✗ Timer is not enabled"
|
echo "✗ Timer is not enabled"
|
||||||
fi
|
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"
|
echo "✓ Timer is active"
|
||||||
else
|
else
|
||||||
echo "✗ Timer is not active"
|
echo "✗ Timer is not active"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Testing current logic:"
|
echo "Testing current logic:"
|
||||||
/usr/local/bin/pc-startup-check.sh
|
/usr/local/bin/pc-startup-check.sh
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show final instructions
|
# Function to show final instructions
|
||||||
show_instructions() {
|
show_instructions() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "PC Startup Monitor Setup Complete"
|
echo "PC Startup Monitor Setup Complete"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Summary:"
|
echo "Summary:"
|
||||||
echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)"
|
echo "✓ Monitoring service created (/etc/systemd/system/pc-startup-monitor.service)"
|
||||||
echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)"
|
echo "✓ Monitoring timer created (/etc/systemd/system/pc-startup-monitor.timer)"
|
||||||
echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)"
|
echo "✓ Monitor script created (/usr/local/bin/pc-startup-check.sh)"
|
||||||
echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)"
|
echo "✓ Management script created (/usr/local/bin/pc-startup-monitor-manager.sh)"
|
||||||
echo "✓ Timer enabled and started"
|
echo "✓ Timer enabled and started"
|
||||||
echo ""
|
echo ""
|
||||||
echo "How it works:"
|
echo "How it works:"
|
||||||
echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday"
|
echo "• Monitors PC startup times on Monday, Friday, Saturday, Sunday"
|
||||||
echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM"
|
echo "• Expects PC to be turned on between 5:00 AM - 8:00 AM"
|
||||||
echo "• Checks daily at 8:30 AM if PC was turned on in expected window"
|
echo "• Checks daily at 8:30 AM if PC was turned on in expected window"
|
||||||
echo "• Shows warning if PC was not turned on during expected time"
|
echo "• Shows warning if PC was not turned on during expected time"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Management commands:"
|
echo "Management commands:"
|
||||||
echo " sudo pc-startup-monitor-manager.sh status - Check status"
|
echo " sudo pc-startup-monitor-manager.sh status - Check status"
|
||||||
echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs"
|
echo " sudo pc-startup-monitor-manager.sh logs - View monitor logs"
|
||||||
echo " sudo pc-startup-monitor-manager.sh test - Test monitor now"
|
echo " sudo pc-startup-monitor-manager.sh test - Test monitor now"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)"
|
echo "Next check: Tomorrow at 8:30 AM (if it's a monitored day)"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to prompt for confirmation
|
# Function to prompt for confirmation
|
||||||
confirm_setup() {
|
confirm_setup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "PC Startup Monitor Setup"
|
echo "PC Startup Monitor Setup"
|
||||||
echo "======================="
|
echo "======================="
|
||||||
echo "This will set up monitoring for PC startup times."
|
echo "This will set up monitoring for PC startup times."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Monitoring schedule:"
|
echo "Monitoring schedule:"
|
||||||
echo "- Days: Monday, Friday, Saturday, Sunday"
|
echo "- Days: Monday, Friday, Saturday, Sunday"
|
||||||
echo "- Expected startup time: 5:00 AM - 8:00 AM"
|
echo "- Expected startup time: 5:00 AM - 8:00 AM"
|
||||||
echo "- Check time: 8:30 AM daily"
|
echo "- Check time: 8:30 AM daily"
|
||||||
echo "- Action: Show warning if PC wasn't started in expected window"
|
echo "- Action: Show warning if PC wasn't started in expected window"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
read -r -p "Do you want to proceed? (y/N): " confirm
|
read -r -p "Do you want to proceed? (y/N): " confirm
|
||||||
|
|
||||||
case "$confirm" in
|
case "$confirm" in
|
||||||
[yY] | [yY][eE][sS])
|
[yY] | [yY][eE][sS])
|
||||||
echo "Proceeding with setup..."
|
echo "Proceeding with setup..."
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Setup cancelled."
|
echo "Setup cancelled."
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
echo "Auto-proceeding with setup (use --interactive to prompt)"
|
echo "Auto-proceeding with setup (use --interactive to prompt)"
|
||||||
echo "Proceeding with setup..."
|
echo "Proceeding with setup..."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution flow
|
# Main execution flow
|
||||||
main() {
|
main() {
|
||||||
# Check for sudo privileges
|
# Check for sudo privileges
|
||||||
check_sudo "$@"
|
check_sudo "$@"
|
||||||
|
|
||||||
# Confirm setup
|
# Confirm setup
|
||||||
confirm_setup
|
confirm_setup
|
||||||
|
|
||||||
# Create all components
|
# Create all components
|
||||||
create_monitoring_service
|
create_monitoring_service
|
||||||
create_monitoring_timer
|
create_monitoring_timer
|
||||||
create_monitoring_script
|
create_monitoring_script
|
||||||
create_management_script
|
create_management_script
|
||||||
|
|
||||||
# Enable services
|
# Enable services
|
||||||
enable_services
|
enable_services
|
||||||
|
|
||||||
# Test setup
|
# Test setup
|
||||||
test_setup
|
test_setup
|
||||||
|
|
||||||
# Show instructions
|
# Show instructions
|
||||||
show_instructions
|
show_instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run main function
|
# Run main function
|
||||||
|
|||||||
@ -13,9 +13,9 @@ LOG_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism/music-parallel
|
|||||||
|
|
||||||
# Main
|
# Main
|
||||||
if focus_app=$(is_focus_app_running); then
|
if focus_app=$(is_focus_app_running); then
|
||||||
log_message "BLOCKED: YouTube Music launch prevented (focus app: $focus_app)" "$LOG_FILE"
|
log_message "BLOCKED: YouTube Music launch prevented (focus app: $focus_app)" "$LOG_FILE"
|
||||||
notify "🚫 YouTube Music Blocked" "Focus mode active ($focus_app)" normal 3000
|
notify "🚫 YouTube Music Blocked" "Focus mode active ($focus_app)" normal 3000
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# No focus app running, launch normally
|
# No focus app running, launch normally
|
||||||
|
|||||||
@ -31,12 +31,12 @@ FORCE_UPDATE=false
|
|||||||
|
|
||||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||||
fail() {
|
fail() {
|
||||||
echo "[ERROR] $*" >&2
|
echo "[ERROR] $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage: $SCRIPT_NAME [options]
|
Usage: $SCRIPT_NAME [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -55,150 +55,150 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--install-dir)
|
--install-dir)
|
||||||
shift
|
shift
|
||||||
[[ $# -gt 0 ]] || fail "--install-dir requires a value"
|
[[ $# -gt 0 ]] || fail "--install-dir requires a value"
|
||||||
INSTALL_ROOT="$1"
|
INSTALL_ROOT="$1"
|
||||||
;;
|
;;
|
||||||
--project)
|
--project)
|
||||||
shift
|
shift
|
||||||
[[ $# -gt 0 ]] || fail "--project requires a path to .uproject"
|
[[ $# -gt 0 ]] || fail "--project requires a path to .uproject"
|
||||||
PROJECT_UPROJECT="$1"
|
PROJECT_UPROJECT="$1"
|
||||||
;;
|
;;
|
||||||
--no-continue)
|
--no-continue)
|
||||||
CONFIGURE_CONTINUE=false
|
CONFIGURE_CONTINUE=false
|
||||||
;;
|
;;
|
||||||
--no-vscode)
|
--no-vscode)
|
||||||
CONFIGURE_VSCODE_USER=false
|
CONFIGURE_VSCODE_USER=false
|
||||||
;;
|
;;
|
||||||
--force-update)
|
--force-update)
|
||||||
FORCE_UPDATE=true
|
FORCE_UPDATE=true
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
fail "Unknown option: $1"
|
fail "Unknown option: $1"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
REPO_DIR="$INSTALL_ROOT/unreal-mcp"
|
REPO_DIR="$INSTALL_ROOT/unreal-mcp"
|
||||||
|
|
||||||
# ---------- Dependencies ----------
|
# ---------- Dependencies ----------
|
||||||
require_cmd() { command -v "$1" >/dev/null 2>&1; }
|
require_cmd() { command -v "$1" > /dev/null 2>&1; }
|
||||||
|
|
||||||
ensure_packages_arch() {
|
ensure_packages_arch() {
|
||||||
# Install with pacman using sudo when needed; keep idempotent with --needed
|
# Install with pacman using sudo when needed; keep idempotent with --needed
|
||||||
local pkgs=(git jq uv python rsync)
|
local pkgs=(git jq uv python rsync)
|
||||||
local to_install=()
|
local to_install=()
|
||||||
for p in "${pkgs[@]}"; do
|
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")
|
to_install+=("$p")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ ${#to_install[@]} -gt 0 ]]; then
|
if [[ ${#to_install[@]} -gt 0 ]]; then
|
||||||
log "Installing packages: ${to_install[*]}"
|
log "Installing packages: ${to_install[*]}"
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
pacman -S --noconfirm --needed "${to_install[@]}"
|
pacman -S --noconfirm --needed "${to_install[@]}"
|
||||||
else
|
else
|
||||||
sudo pacman -S --noconfirm --needed "${to_install[@]}"
|
sudo pacman -S --noconfirm --needed "${to_install[@]}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "All required packages already installed"
|
log "All required packages already installed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_python_version() {
|
check_python_version() {
|
||||||
if require_cmd python; then
|
if require_cmd python; then
|
||||||
local v
|
local v
|
||||||
v=$(python -V 2>&1 | awk '{print $2}')
|
v=$(python -V 2>&1 | awk '{print $2}')
|
||||||
elif require_cmd python3; then
|
elif require_cmd python3; then
|
||||||
local v
|
local v
|
||||||
v=$(python3 -V 2>&1 | awk '{print $2}')
|
v=$(python3 -V 2>&1 | awk '{print $2}')
|
||||||
else
|
else
|
||||||
log "python not found; pacman install will provide it"
|
log "python not found; pacman install will provide it"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
# Require >= 3.12 (Unreal MCP docs)
|
# Require >= 3.12 (Unreal MCP docs)
|
||||||
local major minor
|
local major minor
|
||||||
major=$(echo "$v" | cut -d. -f1)
|
major=$(echo "$v" | cut -d. -f1)
|
||||||
minor=$(echo "$v" | cut -d. -f2)
|
minor=$(echo "$v" | cut -d. -f2)
|
||||||
if ((major < 3 || (major == 3 && minor < 12))); then
|
if ((major < 3 || (major == 3 && minor < 12))); then
|
||||||
log "Python $v detected; installing newer python via pacman"
|
log "Python $v detected; installing newer python via pacman"
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
pacman -S --noconfirm --needed python
|
pacman -S --noconfirm --needed python
|
||||||
else
|
else
|
||||||
sudo pacman -S --noconfirm --needed python
|
sudo pacman -S --noconfirm --needed python
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- Git clone/update ----------
|
# ---------- Git clone/update ----------
|
||||||
setup_repo() {
|
setup_repo() {
|
||||||
mkdir -p "$INSTALL_ROOT"
|
mkdir -p "$INSTALL_ROOT"
|
||||||
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
if [[ ! -d "$REPO_DIR/.git" ]]; then
|
||||||
log "Cloning unreal-mcp into $REPO_DIR"
|
log "Cloning unreal-mcp into $REPO_DIR"
|
||||||
if require_cmd git; then
|
if require_cmd git; then
|
||||||
git clone "$REPO_URL" "$REPO_DIR"
|
git clone "$REPO_URL" "$REPO_DIR"
|
||||||
else
|
else
|
||||||
fail "git is required but not found after install"
|
fail "git is required but not found after install"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "Repo exists at $REPO_DIR"
|
log "Repo exists at $REPO_DIR"
|
||||||
if [[ $FORCE_UPDATE == true ]]; then
|
if [[ $FORCE_UPDATE == true ]]; then
|
||||||
log "Updating repo with --force-update"
|
log "Updating repo with --force-update"
|
||||||
git -C "$REPO_DIR" fetch origin
|
git -C "$REPO_DIR" fetch origin
|
||||||
git -C "$REPO_DIR" reset --hard origin/main
|
git -C "$REPO_DIR" reset --hard origin/main
|
||||||
git -C "$REPO_DIR" pull --rebase --autostash
|
git -C "$REPO_DIR" pull --rebase --autostash
|
||||||
else
|
else
|
||||||
log "Pulling latest changes"
|
log "Pulling latest changes"
|
||||||
git -C "$REPO_DIR" pull --rebase --autostash
|
git -C "$REPO_DIR" pull --rebase --autostash
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure ownership for the real user when script ran via sudo
|
# Ensure ownership for the real user when script ran via sudo
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_ROOT"
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" "$INSTALL_ROOT"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- Launcher ----------
|
# ---------- Launcher ----------
|
||||||
install_launcher() {
|
install_launcher() {
|
||||||
local bin_dir="$USER_HOME/.local/bin"
|
local bin_dir="$USER_HOME/.local/bin"
|
||||||
local python_dir="$REPO_DIR/Python"
|
local python_dir="$REPO_DIR/Python"
|
||||||
local launcher="$bin_dir/unreal-mcp-server"
|
local launcher="$bin_dir/unreal-mcp-server"
|
||||||
mkdir -p "$bin_dir"
|
mkdir -p "$bin_dir"
|
||||||
cat >"$launcher" <<EOF
|
cat > "$launcher" << EOF
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
exec uv --directory "$python_dir" run unreal_mcp_server.py "\${1:-}" < /dev/null
|
exec uv --directory "$python_dir" run unreal_mcp_server.py "\${1:-}" < /dev/null
|
||||||
EOF
|
EOF
|
||||||
chmod +x "$launcher"
|
chmod +x "$launcher"
|
||||||
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$launcher"; fi
|
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$launcher"; fi
|
||||||
log "Installed launcher: $launcher"
|
log "Installed launcher: $launcher"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- VS Code: Continue MCP config ----------
|
# ---------- VS Code: Continue MCP config ----------
|
||||||
configure_continue() {
|
configure_continue() {
|
||||||
if [[ $CONFIGURE_CONTINUE != true ]]; then
|
if [[ $CONFIGURE_CONTINUE != true ]]; then
|
||||||
log "Skipping Continue config (--no-continue)"
|
log "Skipping Continue config (--no-continue)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local cont_dir="$USER_HOME/.continue"
|
local cont_dir="$USER_HOME/.continue"
|
||||||
local cont_cfg="$cont_dir/config.json"
|
local cont_cfg="$cont_dir/config.json"
|
||||||
local python_dir="$REPO_DIR/Python"
|
local python_dir="$REPO_DIR/Python"
|
||||||
mkdir -p "$cont_dir"
|
mkdir -p "$cont_dir"
|
||||||
|
|
||||||
# Base JSON when no config exists
|
# Base JSON when no config exists
|
||||||
local tmp_file
|
local tmp_file
|
||||||
tmp_file="$(mktemp)"
|
tmp_file="$(mktemp)"
|
||||||
if [[ ! -f $cont_cfg ]]; then
|
if [[ ! -f $cont_cfg ]]; then
|
||||||
cat >"$tmp_file" <<JSON
|
cat > "$tmp_file" << JSON
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"unrealMCP": {
|
"unrealMCP": {
|
||||||
@ -208,147 +208,147 @@ configure_continue() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
mv "$tmp_file" "$cont_cfg"
|
mv "$tmp_file" "$cont_cfg"
|
||||||
else
|
else
|
||||||
# Merge using jq: ensure .mcpServers exists, then set/overwrite unrealMCP
|
# Merge using jq: ensure .mcpServers exists, then set/overwrite unrealMCP
|
||||||
if ! require_cmd jq; then
|
if ! require_cmd jq; then
|
||||||
fail "jq is required to merge ~/.continue/config.json"
|
fail "jq is required to merge ~/.continue/config.json"
|
||||||
fi
|
fi
|
||||||
jq --arg dir "$python_dir" '
|
jq --arg dir "$python_dir" '
|
||||||
.mcpServers = (.mcpServers // {}) |
|
.mcpServers = (.mcpServers // {}) |
|
||||||
.mcpServers.unrealMCP = {
|
.mcpServers.unrealMCP = {
|
||||||
command: "uv",
|
command: "uv",
|
||||||
args: ["--directory", $dir, "run", "unreal_mcp_server.py"]
|
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
|
fi
|
||||||
|
|
||||||
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$cont_cfg"; fi
|
if [[ $EUID -eq 0 ]]; then chown "$ACTUAL_USER:$ACTUAL_USER" "$cont_cfg"; fi
|
||||||
log "Configured Continue MCP at: $cont_cfg"
|
log "Configured Continue MCP at: $cont_cfg"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- VS Code user MCP (native) ----------
|
# ---------- VS Code user MCP (native) ----------
|
||||||
configure_vscode_user_mcp() {
|
configure_vscode_user_mcp() {
|
||||||
if [[ $CONFIGURE_VSCODE_USER != true ]]; then
|
if [[ $CONFIGURE_VSCODE_USER != true ]]; then
|
||||||
log "Skipping VS Code user MCP config (--no-vscode)"
|
log "Skipping VS Code user MCP config (--no-vscode)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! require_cmd jq; then
|
if ! require_cmd jq; then
|
||||||
fail "jq is required to compose VS Code --add-mcp JSON and to parse profiles"
|
fail "jq is required to compose VS Code --add-mcp JSON and to parse profiles"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local python_dir="$REPO_DIR/Python"
|
local python_dir="$REPO_DIR/Python"
|
||||||
local json
|
local json
|
||||||
json=$(jq -n --arg dir "$python_dir" '{name:"unrealMCP", command:"uv", args:["--directory", $dir, "run", "unreal_mcp_server.py"]}')
|
json=$(jq -n --arg dir "$python_dir" '{name:"unrealMCP", command:"uv", args:["--directory", $dir, "run", "unreal_mcp_server.py"]}')
|
||||||
|
|
||||||
# Handle multiple VS Code variants if present
|
# Handle multiple VS Code variants if present
|
||||||
local candidates=(code code-insiders codium)
|
local candidates=(code code-insiders codium)
|
||||||
local found_any=false
|
local found_any=false
|
||||||
for cli in "${candidates[@]}"; do
|
for cli in "${candidates[@]}"; do
|
||||||
if ! command -v "$cli" >/dev/null 2>&1; then
|
if ! command -v "$cli" > /dev/null 2>&1; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
found_any=true
|
found_any=true
|
||||||
log "Registering MCP server in VS Code user profile via: $cli --add-mcp"
|
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"
|
log "[$cli] user profile: unrealMCP added/updated"
|
||||||
else
|
else
|
||||||
sed -n '1,200p' "/tmp/${cli}-add-mcp.log" || true
|
sed -n '1,200p' "/tmp/${cli}-add-mcp.log" || true
|
||||||
fail "[$cli] --add-mcp failed for user profile. Ensure your VS Code supports MCP or rerun with --no-vscode."
|
fail "[$cli] --add-mcp failed for user profile. Ensure your VS Code supports MCP or rerun with --no-vscode."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect profiles with 'unreal' (case-insensitive) and add there too
|
# Detect profiles with 'unreal' (case-insensitive) and add there too
|
||||||
local data_dir=""
|
local data_dir=""
|
||||||
case "$cli" in
|
case "$cli" in
|
||||||
code)
|
code)
|
||||||
data_dir="$USER_HOME/.config/Code"
|
data_dir="$USER_HOME/.config/Code"
|
||||||
;;
|
;;
|
||||||
code-insiders)
|
code-insiders)
|
||||||
data_dir="$USER_HOME/.config/Code - Insiders"
|
data_dir="$USER_HOME/.config/Code - Insiders"
|
||||||
;;
|
;;
|
||||||
codium)
|
codium)
|
||||||
data_dir="$USER_HOME/.config/VSCodium"
|
data_dir="$USER_HOME/.config/VSCodium"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
local profiles_json="$data_dir/User/profiles/profiles.json"
|
local profiles_json="$data_dir/User/profiles/profiles.json"
|
||||||
if [[ -f $profiles_json ]]; then
|
if [[ -f $profiles_json ]]; then
|
||||||
# Extract profile names matching /unreal/i
|
# Extract profile names matching /unreal/i
|
||||||
mapfile -t unreal_profiles < <(jq -r '.profiles // [] | .[] | .name // empty | select(test("unreal"; "i"))' "$profiles_json")
|
mapfile -t unreal_profiles < <(jq -r '.profiles // [] | .[] | .name // empty | select(test("unreal"; "i"))' "$profiles_json")
|
||||||
if [[ ${#unreal_profiles[@]} -gt 0 ]]; then
|
if [[ ${#unreal_profiles[@]} -gt 0 ]]; then
|
||||||
log "[$cli] Found profiles with 'unreal': ${unreal_profiles[*]}"
|
log "[$cli] Found profiles with 'unreal': ${unreal_profiles[*]}"
|
||||||
local name
|
local name
|
||||||
for name in "${unreal_profiles[@]}"; do
|
for name in "${unreal_profiles[@]}"; do
|
||||||
log "[$cli] Adding unrealMCP to profile: $name"
|
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"
|
log "[$cli] profile '$name': unrealMCP added/updated"
|
||||||
else
|
else
|
||||||
sed -n '1,200p' "/tmp/${cli}-add-mcp-${name// /_}.log" || true
|
sed -n '1,200p' "/tmp/${cli}-add-mcp-${name// /_}.log" || true
|
||||||
fail "[$cli] --add-mcp failed for profile '$name'."
|
fail "[$cli] --add-mcp failed for profile '$name'."
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
log "[$cli] No VS Code profiles with 'unreal' in name"
|
log "[$cli] No VS Code profiles with 'unreal' in name"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "[$cli] Profiles file not found: $profiles_json (skipping profile-specific adds)"
|
log "[$cli] Profiles file not found: $profiles_json (skipping profile-specific adds)"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $found_any == false ]]; then
|
if [[ $found_any == false ]]; then
|
||||||
fail "VS Code CLI not found (code/code-insiders/codium). Install VS Code and ensure 'code' CLI is available, or run with --no-vscode to skip."
|
fail "VS Code CLI not found (code/code-insiders/codium). Install VS Code and ensure 'code' CLI is available, or run with --no-vscode to skip."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- Unreal Plugin copy (optional) ----------
|
# ---------- Unreal Plugin copy (optional) ----------
|
||||||
install_plugin_into_project() {
|
install_plugin_into_project() {
|
||||||
[[ -n $PROJECT_UPROJECT ]] || return 0
|
[[ -n $PROJECT_UPROJECT ]] || return 0
|
||||||
local upath="$PROJECT_UPROJECT"
|
local upath="$PROJECT_UPROJECT"
|
||||||
if [[ -d $upath ]]; then
|
if [[ -d $upath ]]; then
|
||||||
# Resolve .uproject in the provided directory
|
# 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
|
if [[ ${#_uprojects[@]} -eq 0 ]]; then
|
||||||
fail "--project directory '$upath' contains no .uproject files"
|
fail "--project directory '$upath' contains no .uproject files"
|
||||||
elif [[ ${#_uprojects[@]} -gt 1 ]]; then
|
elif [[ ${#_uprojects[@]} -gt 1 ]]; then
|
||||||
printf '[ERROR] Multiple .uproject files found in %s:\n' "$upath" >&2
|
printf '[ERROR] Multiple .uproject files found in %s:\n' "$upath" >&2
|
||||||
printf ' - %s\n' "${_uprojects[@]}" >&2
|
printf ' - %s\n' "${_uprojects[@]}" >&2
|
||||||
fail "Please pass the specific .uproject path to --project"
|
fail "Please pass the specific .uproject path to --project"
|
||||||
else
|
else
|
||||||
upath="${_uprojects[0]}"
|
upath="${_uprojects[0]}"
|
||||||
log "Resolved .uproject: $upath"
|
log "Resolved .uproject: $upath"
|
||||||
fi
|
fi
|
||||||
elif [[ -f $upath ]]; then
|
elif [[ -f $upath ]]; then
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
fail "--project path does not exist: $upath"
|
fail "--project path does not exist: $upath"
|
||||||
fi
|
fi
|
||||||
if [[ ${upath##*.} != "uproject" ]]; then
|
if [[ ${upath##*.} != "uproject" ]]; then
|
||||||
fail "--project must point to a .uproject file (got: $upath)"
|
fail "--project must point to a .uproject file (got: $upath)"
|
||||||
fi
|
fi
|
||||||
local proj_dir
|
local proj_dir
|
||||||
proj_dir="$(cd "$(dirname "$upath")" && pwd)"
|
proj_dir="$(cd "$(dirname "$upath")" && pwd)"
|
||||||
RESOLVED_PROJECT_DIR="$proj_dir"
|
RESOLVED_PROJECT_DIR="$proj_dir"
|
||||||
local src_plugin="$REPO_DIR/MCPGameProject/Plugins/UnrealMCP"
|
local src_plugin="$REPO_DIR/MCPGameProject/Plugins/UnrealMCP"
|
||||||
local dst_plugin="$proj_dir/Plugins/UnrealMCP"
|
local dst_plugin="$proj_dir/Plugins/UnrealMCP"
|
||||||
if [[ ! -d $src_plugin ]]; then
|
if [[ ! -d $src_plugin ]]; then
|
||||||
fail "Source plugin not found at $src_plugin (did repo layout change?)"
|
fail "Source plugin not found at $src_plugin (did repo layout change?)"
|
||||||
fi
|
fi
|
||||||
mkdir -p "$proj_dir/Plugins"
|
mkdir -p "$proj_dir/Plugins"
|
||||||
log "Copying UnrealMCP plugin to project: $dst_plugin"
|
log "Copying UnrealMCP plugin to project: $dst_plugin"
|
||||||
rsync -a --delete "$src_plugin/" "$dst_plugin/"
|
rsync -a --delete "$src_plugin/" "$dst_plugin/"
|
||||||
# Set ownership back to actual user if run as root
|
# Set ownership back to actual user if run as root
|
||||||
if [[ $EUID -eq 0 ]]; then chown -R "$ACTUAL_USER:$ACTUAL_USER" "$proj_dir/Plugins"; fi
|
if [[ $EUID -eq 0 ]]; then chown -R "$ACTUAL_USER:$ACTUAL_USER" "$proj_dir/Plugins"; fi
|
||||||
log "Plugin installed. Enable it from Unreal Editor (Edit > Plugins) if needed."
|
log "Plugin installed. Enable it from Unreal Editor (Edit > Plugins) if needed."
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------- Summary ----------
|
# ---------- Summary ----------
|
||||||
print_summary() {
|
print_summary() {
|
||||||
local python_dir="$REPO_DIR/Python"
|
local python_dir="$REPO_DIR/Python"
|
||||||
local plugin_dest="N/A"
|
local plugin_dest="N/A"
|
||||||
if [[ -n $RESOLVED_PROJECT_DIR ]]; then
|
if [[ -n $RESOLVED_PROJECT_DIR ]]; then
|
||||||
plugin_dest="$RESOLVED_PROJECT_DIR/Plugins/UnrealMCP"
|
plugin_dest="$RESOLVED_PROJECT_DIR/Plugins/UnrealMCP"
|
||||||
fi
|
fi
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
============================================
|
============================================
|
||||||
Unreal MCP setup complete
|
Unreal MCP setup complete
|
||||||
============================================
|
============================================
|
||||||
@ -381,15 +381,15 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log "Installing prerequisites (Arch Linux)"
|
log "Installing prerequisites (Arch Linux)"
|
||||||
ensure_packages_arch
|
ensure_packages_arch
|
||||||
check_python_version
|
check_python_version
|
||||||
setup_repo
|
setup_repo
|
||||||
install_launcher
|
install_launcher
|
||||||
configure_continue
|
configure_continue
|
||||||
install_plugin_into_project
|
install_plugin_into_project
|
||||||
configure_vscode_user_mcp
|
configure_vscode_user_mcp
|
||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -13,33 +13,33 @@ echo -e "${BLUE}=== Unreal MCP Installer for Arch Linux ===${NC}"
|
|||||||
# Check dependencies
|
# Check dependencies
|
||||||
echo -e "${BLUE}Checking dependencies...${NC}"
|
echo -e "${BLUE}Checking dependencies...${NC}"
|
||||||
for cmd in git python pip; do
|
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}"
|
echo -e "${RED}Error: $cmd is not installed. Please install it (e.g., sudo pacman -S $cmd)${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Get Unreal Project Path
|
# Get Unreal Project Path
|
||||||
PROJECT_PATH="$1"
|
PROJECT_PATH="$1"
|
||||||
if [ -z "$PROJECT_PATH" ]; then
|
if [ -z "$PROJECT_PATH" ]; then
|
||||||
echo -e "${YELLOW}Please enter the path to your Unreal Engine Project (the folder containing .uproject file):${NC}"
|
echo -e "${YELLOW}Please enter the path to your Unreal Engine Project (the folder containing .uproject file):${NC}"
|
||||||
read -r -e -p "> " PROJECT_PATH
|
read -r -e -p "> " PROJECT_PATH
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate path
|
# Validate path
|
||||||
# Expand tilde if present
|
# Expand tilde if present
|
||||||
PROJECT_PATH="${PROJECT_PATH/#\~/$HOME}"
|
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
|
if [ -z "$PROJECT_PATH" ] || [ ! -d "$PROJECT_PATH" ]; then
|
||||||
echo -e "${RED}Error: Invalid directory: $PROJECT_PATH${NC}"
|
echo -e "${RED}Error: Invalid directory: $PROJECT_PATH${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
UPROJECT_FILES=("$PROJECT_PATH"/*.uproject)
|
UPROJECT_FILES=("$PROJECT_PATH"/*.uproject)
|
||||||
if [ ! -e "${UPROJECT_FILES[0]}" ]; then
|
if [ ! -e "${UPROJECT_FILES[0]}" ]; then
|
||||||
echo -e "${RED}Error: No .uproject file found in $PROJECT_PATH${NC}"
|
echo -e "${RED}Error: No .uproject file found in $PROJECT_PATH${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}Target Project: $PROJECT_PATH${NC}"
|
echo -e "${GREEN}Target Project: $PROJECT_PATH${NC}"
|
||||||
@ -51,12 +51,12 @@ mkdir -p "$PLUGINS_DIR"
|
|||||||
# Clone UnrealMCP
|
# Clone UnrealMCP
|
||||||
MCP_PLUGIN_DIR="$PLUGINS_DIR/UnrealMCP"
|
MCP_PLUGIN_DIR="$PLUGINS_DIR/UnrealMCP"
|
||||||
if [ -d "$MCP_PLUGIN_DIR" ]; then
|
if [ -d "$MCP_PLUGIN_DIR" ]; then
|
||||||
echo -e "${BLUE}UnrealMCP already exists. Updating...${NC}"
|
echo -e "${BLUE}UnrealMCP already exists. Updating...${NC}"
|
||||||
cd "$MCP_PLUGIN_DIR"
|
cd "$MCP_PLUGIN_DIR"
|
||||||
git pull
|
git pull
|
||||||
else
|
else
|
||||||
echo -e "${BLUE}Cloning UnrealMCP...${NC}"
|
echo -e "${BLUE}Cloning UnrealMCP...${NC}"
|
||||||
git clone https://github.com/kvick-games/UnrealMCP.git "$MCP_PLUGIN_DIR"
|
git clone https://github.com/kvick-games/UnrealMCP.git "$MCP_PLUGIN_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Setup Python Environment
|
# Setup Python Environment
|
||||||
@ -64,41 +64,41 @@ echo -e "${BLUE}Setting up Python environment...${NC}"
|
|||||||
MCP_DIR="$MCP_PLUGIN_DIR/MCP"
|
MCP_DIR="$MCP_PLUGIN_DIR/MCP"
|
||||||
|
|
||||||
if [ ! -f "$MCP_DIR/unreal_mcp_bridge.py" ]; then
|
if [ ! -f "$MCP_DIR/unreal_mcp_bridge.py" ]; then
|
||||||
echo -e "${RED}Error: unreal_mcp_bridge.py not found in $MCP_DIR. Repository structure might have changed.${NC}"
|
echo -e "${RED}Error: unreal_mcp_bridge.py not found in $MCP_DIR. Repository structure might have changed.${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VENV_DIR="$MCP_DIR/python_env"
|
VENV_DIR="$MCP_DIR/python_env"
|
||||||
|
|
||||||
if [ ! -d "$VENV_DIR" ]; then
|
if [ ! -d "$VENV_DIR" ]; then
|
||||||
echo "Creating virtual environment..."
|
echo "Creating virtual environment..."
|
||||||
python -m venv "$VENV_DIR"
|
python -m venv "$VENV_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
echo "Installing dependencies in virtual environment..."
|
echo "Installing dependencies in virtual environment..."
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
pip install --upgrade pip >/dev/null
|
pip install --upgrade pip > /dev/null
|
||||||
pip install "mcp>=0.1.0" >/dev/null
|
pip install "mcp>=0.1.0" > /dev/null
|
||||||
|
|
||||||
# Patch unreal_mcp_bridge.py for newer mcp package compatibility
|
# Patch unreal_mcp_bridge.py for newer mcp package compatibility
|
||||||
# The newer mcp package (1.x) renamed 'description' parameter to 'instructions'
|
# The newer mcp package (1.x) renamed 'description' parameter to 'instructions'
|
||||||
BRIDGE_SCRIPT="$MCP_DIR/unreal_mcp_bridge.py"
|
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..."
|
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"
|
sed -i 's/description="Unreal Engine integration through the Model Context Protocol"/instructions="Unreal Engine integration through the Model Context Protocol"/' "$BRIDGE_SCRIPT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fix case-sensitive includes for Linux (Windows is case-insensitive, Linux is not)
|
# Fix case-sensitive includes for Linux (Windows is case-insensitive, Linux is not)
|
||||||
echo "Fixing case-sensitive includes for Linux..."
|
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
|
# Create Linux Run Script
|
||||||
RUN_SCRIPT="$MCP_DIR/run_unreal_mcp.sh"
|
RUN_SCRIPT="$MCP_DIR/run_unreal_mcp.sh"
|
||||||
echo -e "${BLUE}Creating run script at $RUN_SCRIPT...${NC}"
|
echo -e "${BLUE}Creating run script at $RUN_SCRIPT...${NC}"
|
||||||
|
|
||||||
cat <<EOF >"$RUN_SCRIPT"
|
cat << EOF > "$RUN_SCRIPT"
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@ -115,7 +115,7 @@ echo -e "${BLUE}=== Configuration Setup ===${NC}"
|
|||||||
|
|
||||||
# Python script to update JSON configs
|
# Python script to update JSON configs
|
||||||
CONFIG_UPDATER_SCRIPT=$(mktemp)
|
CONFIG_UPDATER_SCRIPT=$(mktemp)
|
||||||
cat <<EOF >"$CONFIG_UPDATER_SCRIPT"
|
cat << EOF > "$CONFIG_UPDATER_SCRIPT"
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -164,18 +164,18 @@ CLAUDE_CONFIG="$HOME/.config/Claude/claude_desktop_config.json"
|
|||||||
|
|
||||||
# Function to ask and update
|
# Function to ask and update
|
||||||
update_config() {
|
update_config() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local type="$2"
|
local type="$2"
|
||||||
local name="$3"
|
local name="$3"
|
||||||
|
|
||||||
if [ -f "$path" ] || [ -d "$(dirname "$path")" ]; then
|
if [ -f "$path" ] || [ -d "$(dirname "$path")" ]; then
|
||||||
echo -e "Found $name configuration at: $path"
|
echo -e "Found $name configuration at: $path"
|
||||||
read -p "Do you want to add UnrealMCP to this config? (y/n) " -n 1 -r
|
read -p "Do you want to add UnrealMCP to this config? (y/n) " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
python "$CONFIG_UPDATER_SCRIPT" "$path" "$RUN_SCRIPT" "$type"
|
python "$CONFIG_UPDATER_SCRIPT" "$path" "$RUN_SCRIPT" "$type"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
update_config "$ROO_CODE_CONFIG" "roo_code" "Roo Code (VS Code Extension)"
|
update_config "$ROO_CODE_CONFIG" "roo_code" "Roo Code (VS Code Extension)"
|
||||||
@ -189,8 +189,8 @@ mkdir -p "$VSCODE_DIR"
|
|||||||
MCP_JSON="$VSCODE_DIR/mcp.json"
|
MCP_JSON="$VSCODE_DIR/mcp.json"
|
||||||
|
|
||||||
if [ ! -f "$MCP_JSON" ]; then
|
if [ ! -f "$MCP_JSON" ]; then
|
||||||
echo -e "${BLUE}Creating workspace MCP config at $MCP_JSON...${NC}"
|
echo -e "${BLUE}Creating workspace MCP config at $MCP_JSON...${NC}"
|
||||||
cat <<EOF >"$MCP_JSON"
|
cat << EOF > "$MCP_JSON"
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"unreal": {
|
"unreal": {
|
||||||
@ -201,23 +201,23 @@ if [ ! -f "$MCP_JSON" ]; then
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}Workspace MCP config already exists at $MCP_JSON. Skipping overwrite.${NC}"
|
echo -e "${YELLOW}Workspace MCP config already exists at $MCP_JSON. Skipping overwrite.${NC}"
|
||||||
echo "Ensure it contains the following configuration:"
|
echo "Ensure it contains the following configuration:"
|
||||||
echo "\"unreal\": { \"command\": \"$RUN_SCRIPT\", \"args\": [] }"
|
echo "\"unreal\": { \"command\": \"$RUN_SCRIPT\", \"args\": [] }"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}=== Build Instructions ===${NC}"
|
echo -e "${BLUE}=== Build Instructions ===${NC}"
|
||||||
echo "1. You need to regenerate project files."
|
echo "1. You need to regenerate project files."
|
||||||
if [ -f "$PROJECT_PATH/GenerateProjectFiles.sh" ]; then
|
if [ -f "$PROJECT_PATH/GenerateProjectFiles.sh" ]; then
|
||||||
echo " Found GenerateProjectFiles.sh in project root."
|
echo " Found GenerateProjectFiles.sh in project root."
|
||||||
read -p " Do you want to run it now? (y/n) " -n 1 -r
|
read -p " Do you want to run it now? (y/n) " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
cd "$PROJECT_PATH"
|
cd "$PROJECT_PATH"
|
||||||
./GenerateProjectFiles.sh
|
./GenerateProjectFiles.sh
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " Run your engine's GenerateProjectFiles.sh or right-click .uproject -> Generate Project Files."
|
echo " Run your engine's GenerateProjectFiles.sh or right-click .uproject -> Generate Project Files."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "2. Build the project (e.g., run 'make' in the project root)."
|
echo "2. Build the project (e.g., run 'make' in the project root)."
|
||||||
@ -232,7 +232,7 @@ echo -e "${YELLOW}$RUN_SCRIPT${NC}"
|
|||||||
echo
|
echo
|
||||||
echo "For VS Code (User Settings), add this to your settings.json:"
|
echo "For VS Code (User Settings), add this to your settings.json:"
|
||||||
echo -e "${GREEN}"
|
echo -e "${GREEN}"
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"unreal": {
|
"unreal": {
|
||||||
"command": "$RUN_SCRIPT",
|
"command": "$RUN_SCRIPT",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -12,11 +12,11 @@ source "$SCRIPT_DIR/../lib/common.sh"
|
|||||||
|
|
||||||
# Function to check and request sudo privileges for package installation
|
# Function to check and request sudo privileges for package installation
|
||||||
check_sudo() {
|
check_sudo() {
|
||||||
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
|
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
|
||||||
echo "Package installation requires sudo privileges."
|
echo "Package installation requires sudo privileges."
|
||||||
echo "Requesting sudo access..."
|
echo "Requesting sudo access..."
|
||||||
exec sudo "$0" "$@"
|
exec sudo "$0" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the actual user (even when running with sudo)
|
# Get the actual user (even when running with sudo)
|
||||||
@ -31,180 +31,180 @@ echo "User home: $USER_HOME"
|
|||||||
|
|
||||||
# Function to check if ActivityWatch is installed
|
# Function to check if ActivityWatch is installed
|
||||||
check_activitywatch_installed() {
|
check_activitywatch_installed() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Checking ActivityWatch Installation..."
|
echo "1. Checking ActivityWatch Installation..."
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
# Check if activitywatch-bin is installed via pacman
|
# 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"
|
echo "✓ activitywatch-bin package is installed"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if aw-qt binary exists in common locations
|
# Check if aw-qt binary exists in common locations
|
||||||
local common_paths=(
|
local common_paths=(
|
||||||
"/usr/bin/aw-qt"
|
"/usr/bin/aw-qt"
|
||||||
"/usr/local/bin/aw-qt"
|
"/usr/local/bin/aw-qt"
|
||||||
"$USER_HOME/.local/bin/aw-qt"
|
"$USER_HOME/.local/bin/aw-qt"
|
||||||
"$USER_HOME/activitywatch/aw-qt"
|
"$USER_HOME/activitywatch/aw-qt"
|
||||||
)
|
)
|
||||||
|
|
||||||
for path in "${common_paths[@]}"; do
|
for path in "${common_paths[@]}"; do
|
||||||
if [[ -x $path ]]; then
|
if [[ -x $path ]]; then
|
||||||
echo "✓ ActivityWatch found at: $path"
|
echo "✓ ActivityWatch found at: $path"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "✗ ActivityWatch not found"
|
echo "✗ ActivityWatch not found"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to install ActivityWatch
|
# Function to install ActivityWatch
|
||||||
install_activitywatch() {
|
install_activitywatch() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Installing ActivityWatch..."
|
echo "2. Installing ActivityWatch..."
|
||||||
echo "============================="
|
echo "============================="
|
||||||
|
|
||||||
# Check if we need sudo for installation
|
# Check if we need sudo for installation
|
||||||
check_sudo "install"
|
check_sudo "install"
|
||||||
|
|
||||||
echo "Installing activitywatch-bin from AUR..."
|
echo "Installing activitywatch-bin from AUR..."
|
||||||
|
|
||||||
# Check if an AUR helper is available
|
# Check if an AUR helper is available
|
||||||
local aur_helpers=("yay" "paru" "makepkg")
|
local aur_helpers=("yay" "paru" "makepkg")
|
||||||
local helper_found=""
|
local helper_found=""
|
||||||
|
|
||||||
for helper in "${aur_helpers[@]}"; do
|
for helper in "${aur_helpers[@]}"; do
|
||||||
if command -v "$helper" &>/dev/null; then
|
if command -v "$helper" &> /dev/null; then
|
||||||
helper_found="$helper"
|
helper_found="$helper"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n $helper_found && $helper_found != "makepkg" ]]; then
|
if [[ -n $helper_found && $helper_found != "makepkg" ]]; then
|
||||||
echo "Using AUR helper: $helper_found"
|
echo "Using AUR helper: $helper_found"
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
# Running as root, need to install as user
|
# Running as root, need to install as user
|
||||||
sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin
|
sudo -u "$ACTUAL_USER" "$helper_found" -S --noconfirm activitywatch-bin
|
||||||
else
|
else
|
||||||
"$helper_found" -S --noconfirm activitywatch-bin
|
"$helper_found" -S --noconfirm activitywatch-bin
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "No AUR helper found. Installing manually with makepkg..."
|
echo "No AUR helper found. Installing manually with makepkg..."
|
||||||
install_activitywatch_manual
|
install_activitywatch_manual
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ ActivityWatch installation completed"
|
echo "✓ ActivityWatch installation completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to manually install ActivityWatch via makepkg
|
# Function to manually install ActivityWatch via makepkg
|
||||||
install_activitywatch_manual() {
|
install_activitywatch_manual() {
|
||||||
local temp_dir="/tmp/activitywatch-install"
|
local temp_dir="/tmp/activitywatch-install"
|
||||||
local original_user="$ACTUAL_USER"
|
local original_user="$ACTUAL_USER"
|
||||||
|
|
||||||
# Create temp directory
|
# Create temp directory
|
||||||
mkdir -p "$temp_dir"
|
mkdir -p "$temp_dir"
|
||||||
cd "$temp_dir"
|
cd "$temp_dir"
|
||||||
|
|
||||||
# Download PKGBUILD
|
# 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 .
|
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
|
||||||
else
|
else
|
||||||
echo "Installing git..."
|
echo "Installing git..."
|
||||||
pacman -S --noconfirm git
|
pacman -S --noconfirm git
|
||||||
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
|
sudo -u "$original_user" git clone https://aur.archlinux.org/activitywatch-bin.git .
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build and install package
|
# Build and install package
|
||||||
sudo -u "$original_user" makepkg -si --noconfirm
|
sudo -u "$original_user" makepkg -si --noconfirm
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
cd /
|
cd /
|
||||||
rm -rf "$temp_dir"
|
rm -rf "$temp_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if ActivityWatch is running
|
# Function to check if ActivityWatch is running
|
||||||
check_activitywatch_running() {
|
check_activitywatch_running() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Checking ActivityWatch Status..."
|
echo "3. Checking ActivityWatch Status..."
|
||||||
echo "=================================="
|
echo "=================================="
|
||||||
|
|
||||||
# Check for aw-qt process
|
# 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"
|
echo "✓ ActivityWatch (aw-qt) is running"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for aw-server process
|
# 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"
|
echo "✓ ActivityWatch server is running"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✗ ActivityWatch is not running"
|
echo "✗ ActivityWatch is not running"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to start ActivityWatch
|
# Function to start ActivityWatch
|
||||||
start_activitywatch() {
|
start_activitywatch() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Starting ActivityWatch..."
|
echo "4. Starting ActivityWatch..."
|
||||||
echo "==========================="
|
echo "==========================="
|
||||||
|
|
||||||
# Find aw-qt executable
|
# Find aw-qt executable
|
||||||
local aw_qt_path=""
|
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)"
|
aw_qt_path="$(which aw-qt)"
|
||||||
elif [[ -x "/usr/bin/aw-qt" ]]; then
|
elif [[ -x "/usr/bin/aw-qt" ]]; then
|
||||||
aw_qt_path="/usr/bin/aw-qt"
|
aw_qt_path="/usr/bin/aw-qt"
|
||||||
else
|
else
|
||||||
echo "✗ Could not find aw-qt executable"
|
echo "✗ Could not find aw-qt executable"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting ActivityWatch as user: $ACTUAL_USER"
|
echo "Starting ActivityWatch as user: $ACTUAL_USER"
|
||||||
echo "Using aw-qt from: $aw_qt_path"
|
echo "Using aw-qt from: $aw_qt_path"
|
||||||
|
|
||||||
# Start as the actual user in the background
|
# Start as the actual user in the background
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
# Running as root, start as user
|
# Running as root, start as user
|
||||||
sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" &
|
sudo -u "$ACTUAL_USER" env DISPLAY=:0 "$aw_qt_path" &
|
||||||
else
|
else
|
||||||
# Running as user
|
# Running as user
|
||||||
"$aw_qt_path" &
|
"$aw_qt_path" &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Give it time to start
|
# Give it time to start
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if check_activitywatch_running >/dev/null 2>&1; then
|
if check_activitywatch_running > /dev/null 2>&1; then
|
||||||
echo "✓ ActivityWatch started successfully"
|
echo "✓ ActivityWatch started successfully"
|
||||||
else
|
else
|
||||||
echo "! ActivityWatch may be starting (check system tray)"
|
echo "! ActivityWatch may be starting (check system tray)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to setup autostart
|
# Function to setup autostart
|
||||||
setup_autostart() {
|
setup_autostart() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "5. Setting Up Autostart..."
|
echo "5. Setting Up Autostart..."
|
||||||
echo "========================="
|
echo "========================="
|
||||||
|
|
||||||
local autostart_dir="$USER_HOME/.config/autostart"
|
local autostart_dir="$USER_HOME/.config/autostart"
|
||||||
local desktop_file="$autostart_dir/activitywatch.desktop"
|
local desktop_file="$autostart_dir/activitywatch.desktop"
|
||||||
local i3_config="$USER_HOME/.config/i3/config"
|
local i3_config="$USER_HOME/.config/i3/config"
|
||||||
|
|
||||||
# Method 1: XDG Autostart (works with most desktop environments)
|
# Method 1: XDG Autostart (works with most desktop environments)
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir"
|
sudo -u "$ACTUAL_USER" mkdir -p "$autostart_dir"
|
||||||
else
|
else
|
||||||
mkdir -p "$autostart_dir"
|
mkdir -p "$autostart_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create desktop file for autostart
|
# Create desktop file for autostart
|
||||||
cat >"$desktop_file" <<EOF
|
cat > "$desktop_file" << EOF
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=ActivityWatch
|
Name=ActivityWatch
|
||||||
@ -219,60 +219,60 @@ Terminal=false
|
|||||||
Categories=Utility;
|
Categories=Utility;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Set proper ownership if running as root
|
# Set proper ownership if running as root
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file"
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$desktop_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Created XDG autostart entry: $desktop_file"
|
echo "✓ Created XDG autostart entry: $desktop_file"
|
||||||
|
|
||||||
# Method 2: i3 config autostart (specific to i3)
|
# Method 2: i3 config autostart (specific to i3)
|
||||||
if [[ -f $i3_config ]]; then
|
if [[ -f $i3_config ]]; then
|
||||||
# Check if autostart entry already exists
|
# Check if autostart entry already exists
|
||||||
if ! grep -q "aw-qt" "$i3_config"; then
|
if ! grep -q "aw-qt" "$i3_config"; then
|
||||||
# Add autostart entry to i3 config
|
# Add autostart entry to i3 config
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
# Running as root
|
# Running as root
|
||||||
sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config'
|
sudo -u "$ACTUAL_USER" bash -c "cat <<'EOF' >> '$i3_config'
|
||||||
|
|
||||||
# Auto-start ActivityWatch
|
# Auto-start ActivityWatch
|
||||||
exec --no-startup-id aw-qt
|
exec --no-startup-id aw-qt
|
||||||
EOF"
|
EOF"
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf '\n'
|
printf '\n'
|
||||||
printf '# Auto-start ActivityWatch\n'
|
printf '# Auto-start ActivityWatch\n'
|
||||||
printf 'exec --no-startup-id aw-qt\n'
|
printf 'exec --no-startup-id aw-qt\n'
|
||||||
} >>"$i3_config"
|
} >> "$i3_config"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Added ActivityWatch to i3 config autostart"
|
echo "✓ Added ActivityWatch to i3 config autostart"
|
||||||
else
|
else
|
||||||
echo "✓ ActivityWatch autostart already exists in i3 config"
|
echo "✓ ActivityWatch autostart already exists in i3 config"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "! i3 config not found at $i3_config"
|
echo "! i3 config not found at $i3_config"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create i3blocks status script
|
# Function to create i3blocks status script
|
||||||
create_i3blocks_status() {
|
create_i3blocks_status() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Creating i3blocks Status Script..."
|
echo "6. Creating i3blocks Status Script..."
|
||||||
echo "===================================="
|
echo "===================================="
|
||||||
|
|
||||||
local i3blocks_dir="$USER_HOME/.config/i3blocks"
|
local i3blocks_dir="$USER_HOME/.config/i3blocks"
|
||||||
local status_script="$i3blocks_dir/activitywatch_status.sh"
|
local status_script="$i3blocks_dir/activitywatch_status.sh"
|
||||||
|
|
||||||
# Create i3blocks directory if it doesn't exist
|
# Create i3blocks directory if it doesn't exist
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir"
|
sudo -u "$ACTUAL_USER" mkdir -p "$i3blocks_dir"
|
||||||
else
|
else
|
||||||
mkdir -p "$i3blocks_dir"
|
mkdir -p "$i3blocks_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create the status script
|
# Create the status script
|
||||||
cat >"$status_script" <<'EOF'
|
cat > "$status_script" << 'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ActivityWatch status script for i3blocks
|
# ActivityWatch status script for i3blocks
|
||||||
# Shows ActivityWatch installation and running status
|
# Shows ActivityWatch installation and running status
|
||||||
@ -323,134 +323,134 @@ else
|
|||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$status_script"
|
chmod +x "$status_script"
|
||||||
|
|
||||||
# Set proper ownership if running as root
|
# Set proper ownership if running as root
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script"
|
chown "$ACTUAL_USER:$ACTUAL_USER" "$status_script"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Created i3blocks status script: $status_script"
|
echo "✓ Created i3blocks status script: $status_script"
|
||||||
|
|
||||||
# Show configuration instructions
|
# Show configuration instructions
|
||||||
echo ""
|
echo ""
|
||||||
echo "To add to your i3blocks config, add this block:"
|
echo "To add to your i3blocks config, add this block:"
|
||||||
echo ""
|
echo ""
|
||||||
echo "[activitywatch]"
|
echo "[activitywatch]"
|
||||||
echo "command=~/.config/i3blocks/activitywatch_status.sh"
|
echo "command=~/.config/i3blocks/activitywatch_status.sh"
|
||||||
echo "interval=10"
|
echo "interval=10"
|
||||||
echo "color=#FFFFFF"
|
echo "color=#FFFFFF"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to test the setup
|
# Function to test the setup
|
||||||
test_setup() {
|
test_setup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "7. Testing Setup..."
|
echo "7. Testing Setup..."
|
||||||
echo "=================="
|
echo "=================="
|
||||||
|
|
||||||
echo "Installation status:"
|
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"
|
echo "✓ ActivityWatch is installed"
|
||||||
else
|
else
|
||||||
echo "✗ ActivityWatch is not installed"
|
echo "✗ ActivityWatch is not installed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Running status:"
|
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"
|
echo "✓ ActivityWatch is running"
|
||||||
else
|
else
|
||||||
echo "✗ ActivityWatch is not running"
|
echo "✗ ActivityWatch is not running"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Autostart files:"
|
echo "Autostart files:"
|
||||||
if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then
|
if [[ -f "$USER_HOME/.config/autostart/activitywatch.desktop" ]]; then
|
||||||
echo "✓ XDG autostart file exists"
|
echo "✓ XDG autostart file exists"
|
||||||
else
|
else
|
||||||
echo "✗ XDG autostart file missing"
|
echo "✗ XDG autostart file missing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then
|
if [[ -f "$USER_HOME/.config/i3/config" ]] && grep -q "aw-qt" "$USER_HOME/.config/i3/config"; then
|
||||||
echo "✓ i3 autostart configured"
|
echo "✓ i3 autostart configured"
|
||||||
else
|
else
|
||||||
echo "! i3 autostart may not be configured"
|
echo "! i3 autostart may not be configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "i3blocks status script:"
|
echo "i3blocks status script:"
|
||||||
if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then
|
if [[ -x "$USER_HOME/.config/i3blocks/activitywatch_status.sh" ]]; then
|
||||||
echo "✓ i3blocks status script created"
|
echo "✓ i3blocks status script created"
|
||||||
echo "Testing status script:"
|
echo "Testing status script:"
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh"
|
sudo -u "$ACTUAL_USER" "$USER_HOME/.config/i3blocks/activitywatch_status.sh"
|
||||||
else
|
else
|
||||||
"$USER_HOME/.config/i3blocks/activitywatch_status.sh"
|
"$USER_HOME/.config/i3blocks/activitywatch_status.sh"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "✗ i3blocks status script missing"
|
echo "✗ i3blocks status script missing"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show final instructions
|
# Function to show final instructions
|
||||||
show_instructions() {
|
show_instructions() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "ActivityWatch Setup Complete"
|
echo "ActivityWatch Setup Complete"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Summary:"
|
echo "Summary:"
|
||||||
echo "✓ ActivityWatch installation checked/completed"
|
echo "✓ ActivityWatch installation checked/completed"
|
||||||
echo "✓ ActivityWatch startup configured"
|
echo "✓ ActivityWatch startup configured"
|
||||||
echo "✓ Autostart configured (XDG + i3)"
|
echo "✓ Autostart configured (XDG + i3)"
|
||||||
echo "✓ i3blocks status script created"
|
echo "✓ i3blocks status script created"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
echo "1. Add the i3blocks configuration to your config file:"
|
echo "1. Add the i3blocks configuration to your config file:"
|
||||||
echo " ~/.config/i3blocks/config"
|
echo " ~/.config/i3blocks/config"
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Reload i3 configuration:"
|
echo "2. Reload i3 configuration:"
|
||||||
echo " Super+Shift+R"
|
echo " Super+Shift+R"
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. ActivityWatch web interface should be available at:"
|
echo "3. ActivityWatch web interface should be available at:"
|
||||||
echo " http://localhost:5600"
|
echo " http://localhost:5600"
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Check system tray for ActivityWatch icon"
|
echo "4. Check system tray for ActivityWatch icon"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Files created:"
|
echo "Files created:"
|
||||||
echo " ~/.config/autostart/activitywatch.desktop"
|
echo " ~/.config/autostart/activitywatch.desktop"
|
||||||
echo " ~/.config/i3blocks/activitywatch_status.sh"
|
echo " ~/.config/i3blocks/activitywatch_status.sh"
|
||||||
echo " ~/.config/i3/config (modified)"
|
echo " ~/.config/i3/config (modified)"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution flow
|
# Main execution flow
|
||||||
main() {
|
main() {
|
||||||
local need_install=false
|
local need_install=false
|
||||||
local need_start=false
|
local need_start=false
|
||||||
|
|
||||||
# Check installation
|
# Check installation
|
||||||
if ! check_activitywatch_installed; then
|
if ! check_activitywatch_installed; then
|
||||||
need_install=true
|
need_install=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install if needed
|
# Install if needed
|
||||||
if [[ $need_install == true ]]; then
|
if [[ $need_install == true ]]; then
|
||||||
install_activitywatch
|
install_activitywatch
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if running
|
# Check if running
|
||||||
if ! check_activitywatch_running; then
|
if ! check_activitywatch_running; then
|
||||||
need_start=true
|
need_start=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start if needed
|
# Start if needed
|
||||||
if [[ $need_start == true ]]; then
|
if [[ $need_start == true ]]; then
|
||||||
start_activitywatch
|
start_activitywatch
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Always set up autostart and i3blocks (in case they're missing)
|
# Always set up autostart and i3blocks (in case they're missing)
|
||||||
setup_autostart
|
setup_autostart
|
||||||
create_i3blocks_status
|
create_i3blocks_status
|
||||||
test_setup
|
test_setup
|
||||||
show_instructions
|
show_instructions
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run main function
|
# Run main function
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@ NC='\033[0m' # No Color
|
|||||||
CHECK_ONLY=false
|
CHECK_ONLY=false
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
fix_anki.sh - Fix Anki startup issues
|
fix_anki.sh - Fix Anki startup issues
|
||||||
|
|
||||||
Usage: $(basename "$0") [OPTIONS]
|
Usage: $(basename "$0") [OPTIONS]
|
||||||
@ -48,177 +48,177 @@ log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|||||||
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
|
||||||
check_anki_installed() {
|
check_anki_installed() {
|
||||||
if pacman -Qi anki-git &>/dev/null; then
|
if pacman -Qi anki-git &> /dev/null; then
|
||||||
echo "anki-git"
|
echo "anki-git"
|
||||||
elif pacman -Qi anki &>/dev/null; then
|
elif pacman -Qi anki &> /dev/null; then
|
||||||
echo "anki"
|
echo "anki"
|
||||||
elif pacman -Qi anki-bin &>/dev/null; then
|
elif pacman -Qi anki-bin &> /dev/null; then
|
||||||
echo "anki-bin"
|
echo "anki-bin"
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_system_python_version() {
|
get_system_python_version() {
|
||||||
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||||
}
|
}
|
||||||
|
|
||||||
get_anki_python_version() {
|
get_anki_python_version() {
|
||||||
local anki_pkg="$1"
|
local anki_pkg="$1"
|
||||||
local anki_path
|
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"
|
echo "$anki_path"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_aqt_conflict() {
|
check_aqt_conflict() {
|
||||||
local sys_python="$1"
|
local sys_python="$1"
|
||||||
local aqt_path="/usr/lib/python${sys_python}/site-packages/aqt/__init__.py"
|
local aqt_path="/usr/lib/python${sys_python}/site-packages/aqt/__init__.py"
|
||||||
|
|
||||||
if [[ -f "$aqt_path" ]]; then
|
if [[ -f $aqt_path ]]; then
|
||||||
if grep -q "aqtinstall" "$aqt_path" 2>/dev/null; then
|
if grep -q "aqtinstall" "$aqt_path" 2> /dev/null; then
|
||||||
echo "aqtinstall"
|
echo "aqtinstall"
|
||||||
elif grep -q "anki" "$aqt_path" 2>/dev/null; then
|
elif grep -q "anki" "$aqt_path" 2> /dev/null; then
|
||||||
echo "anki"
|
echo "anki"
|
||||||
else
|
else
|
||||||
echo "unknown"
|
echo "unknown"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "none"
|
echo "none"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--check)
|
--check)
|
||||||
CHECK_ONLY=true
|
CHECK_ONLY=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown option: $1"
|
log_error "Unknown option: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
log_info "Checking Anki installation..."
|
log_info "Checking Anki installation..."
|
||||||
|
|
||||||
# Check which Anki package is installed
|
# Check which Anki package is installed
|
||||||
local anki_pkg
|
local anki_pkg
|
||||||
anki_pkg=$(check_anki_installed)
|
anki_pkg=$(check_anki_installed)
|
||||||
if [[ -z "$anki_pkg" ]]; then
|
if [[ -z $anki_pkg ]]; then
|
||||||
log_error "Anki is not installed"
|
log_error "Anki is not installed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
log_info "Found Anki package: $anki_pkg"
|
log_info "Found Anki package: $anki_pkg"
|
||||||
|
|
||||||
# Get Python versions
|
# Get Python versions
|
||||||
local sys_python anki_python
|
local sys_python anki_python
|
||||||
sys_python=$(get_system_python_version)
|
sys_python=$(get_system_python_version)
|
||||||
anki_python=$(get_anki_python_version "$anki_pkg")
|
anki_python=$(get_anki_python_version "$anki_pkg")
|
||||||
|
|
||||||
log_info "System Python version: $sys_python"
|
log_info "System Python version: $sys_python"
|
||||||
log_info "Anki built for Python: ${anki_python:-unknown}"
|
log_info "Anki built for Python: ${anki_python:-unknown}"
|
||||||
|
|
||||||
local issues_found=false
|
local issues_found=false
|
||||||
|
|
||||||
# Check for Python version mismatch
|
# 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 "Python version mismatch detected!"
|
||||||
log_warn " Anki was built for Python $anki_python but system runs Python $sys_python"
|
log_warn " Anki was built for Python $anki_python but system runs Python $sys_python"
|
||||||
issues_found=true
|
issues_found=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for aqt namespace conflict
|
# Check for aqt namespace conflict
|
||||||
local aqt_owner
|
local aqt_owner
|
||||||
aqt_owner=$(check_aqt_conflict "$sys_python")
|
aqt_owner=$(check_aqt_conflict "$sys_python")
|
||||||
case "$aqt_owner" in
|
case "$aqt_owner" in
|
||||||
aqtinstall)
|
aqtinstall)
|
||||||
log_warn "aqt namespace conflict detected!"
|
log_warn "aqt namespace conflict detected!"
|
||||||
log_warn " python-aqtinstall owns /usr/lib/python${sys_python}/site-packages/aqt/"
|
log_warn " python-aqtinstall owns /usr/lib/python${sys_python}/site-packages/aqt/"
|
||||||
log_warn " This conflicts with Anki's aqt module"
|
log_warn " This conflicts with Anki's aqt module"
|
||||||
issues_found=true
|
issues_found=true
|
||||||
;;
|
;;
|
||||||
anki)
|
anki)
|
||||||
log_success "aqt module belongs to Anki (correct)"
|
log_success "aqt module belongs to Anki (correct)"
|
||||||
;;
|
;;
|
||||||
none)
|
none)
|
||||||
if [[ "$sys_python" != "$anki_python" ]]; then
|
if [[ $sys_python != "$anki_python" ]]; then
|
||||||
log_warn "No aqt module found for Python $sys_python"
|
log_warn "No aqt module found for Python $sys_python"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_warn "Unknown aqt module owner"
|
log_warn "Unknown aqt module owner"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Test if Anki actually works
|
# Test if Anki actually works
|
||||||
log_info "Testing Anki startup..."
|
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"
|
log_success "Anki imports work correctly"
|
||||||
if [[ "$issues_found" == "false" ]]; then
|
if [[ $issues_found == "false" ]]; then
|
||||||
log_success "No issues found with Anki installation"
|
log_success "No issues found with Anki installation"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_error "Anki import test failed"
|
log_error "Anki import test failed"
|
||||||
issues_found=true
|
issues_found=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$CHECK_ONLY" == "true" ]]; then
|
if [[ $CHECK_ONLY == "true" ]]; then
|
||||||
if [[ "$issues_found" == "true" ]]; then
|
if [[ $issues_found == "true" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Issues detected. Run without --check to fix."
|
log_info "Issues detected. Run without --check to fix."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Apply fixes
|
# Apply fixes
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Applying fixes..."
|
log_info "Applying fixes..."
|
||||||
|
|
||||||
# Check if python-aqtinstall is installed and remove it if nothing depends on it
|
# 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
|
local required_by
|
||||||
required_by=$(pacman -Qi python-aqtinstall | grep "Required By" | cut -d: -f2 | xargs)
|
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)..."
|
log_info "Removing python-aqtinstall (conflicts with Anki)..."
|
||||||
sudo pacman -R --noconfirm python-aqtinstall
|
sudo pacman -R --noconfirm python-aqtinstall
|
||||||
else
|
else
|
||||||
log_warn "python-aqtinstall is required by: $required_by"
|
log_warn "python-aqtinstall is required by: $required_by"
|
||||||
log_warn "Cannot remove automatically. You may need to resolve this manually."
|
log_warn "Cannot remove automatically. You may need to resolve this manually."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Rebuild anki package
|
# Rebuild anki package
|
||||||
if [[ "$anki_pkg" == "anki-git" ]]; then
|
if [[ $anki_pkg == "anki-git" ]]; then
|
||||||
log_info "Rebuilding anki-git for Python $sys_python..."
|
log_info "Rebuilding anki-git for Python $sys_python..."
|
||||||
yay -S anki-git --rebuild --noconfirm
|
yay -S anki-git --rebuild --noconfirm
|
||||||
elif [[ "$anki_pkg" == "anki" ]]; then
|
elif [[ $anki_pkg == "anki" ]]; then
|
||||||
log_info "Reinstalling anki..."
|
log_info "Reinstalling anki..."
|
||||||
sudo pacman -S anki --noconfirm
|
sudo pacman -S anki --noconfirm
|
||||||
else
|
else
|
||||||
log_warn "Package $anki_pkg may need manual rebuild"
|
log_warn "Package $anki_pkg may need manual rebuild"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify fix
|
# Verify fix
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Verifying fix..."
|
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!"
|
log_success "Anki is now working!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "You can start Anki with: anki"
|
echo "You can start Anki with: anki"
|
||||||
else
|
else
|
||||||
log_error "Fix may not have worked. Please check manually."
|
log_error "Fix may not have worked. Please check manually."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -14,29 +14,29 @@ ORGANIZE_SCRIPT="/home/kuhy/linux-configuration/scripts/utils/organize_downloads
|
|||||||
TARGET_USER="kuhy"
|
TARGET_USER="kuhy"
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
log "This script needs to be run as root."
|
log "This script needs to be run as root."
|
||||||
log "Re-executing with sudo..."
|
log "Re-executing with sudo..."
|
||||||
exec sudo "$0" "$@"
|
exec sudo "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Fixing media-organizer.service..."
|
log "Fixing media-organizer.service..."
|
||||||
|
|
||||||
# Verify the organize_downloads.sh script exists
|
# Verify the organize_downloads.sh script exists
|
||||||
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
|
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
|
||||||
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
|
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Stop the service if running (ignore errors)
|
# 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
|
# Recreate the service file with correct configuration
|
||||||
cat >"$SERVICE_FILE" <<EOF
|
cat > "$SERVICE_FILE" << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Media File Organizer
|
Description=Media File Organizer
|
||||||
After=graphical-session.target
|
After=graphical-session.target
|
||||||
@ -62,7 +62,7 @@ systemctl daemon-reload
|
|||||||
log "Reloaded systemd daemon"
|
log "Reloaded systemd daemon"
|
||||||
|
|
||||||
# Reset the failed state
|
# 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"
|
log "Reset failed state"
|
||||||
|
|
||||||
# Re-enable the service
|
# Re-enable the service
|
||||||
@ -72,9 +72,9 @@ log "Service enabled"
|
|||||||
# Optionally start the service to verify it works
|
# Optionally start the service to verify it works
|
||||||
log "Starting service to verify fix..."
|
log "Starting service to verify fix..."
|
||||||
if systemctl start "$SERVICE_NAME.service"; then
|
if systemctl start "$SERVICE_NAME.service"; then
|
||||||
log "SUCCESS: media-organizer.service started successfully!"
|
log "SUCCESS: media-organizer.service started successfully!"
|
||||||
else
|
else
|
||||||
log "WARNING: Service still has issues. Check: journalctl -u $SERVICE_NAME"
|
log "WARNING: Service still has issues. Check: journalctl -u $SERVICE_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show current status
|
# Show current status
|
||||||
|
|||||||
@ -32,7 +32,7 @@ YELLOW='\033[1;33m'
|
|||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
fix_thorium.sh - Fix Thorium Browser crashes and startup issues
|
fix_thorium.sh - Fix Thorium Browser crashes and startup issues
|
||||||
|
|
||||||
Usage: $(basename "$0") [OPTIONS]
|
Usage: $(basename "$0") [OPTIONS]
|
||||||
@ -60,318 +60,318 @@ EOF
|
|||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--aggressive)
|
--aggressive)
|
||||||
AGGRESSIVE=true
|
AGGRESSIVE=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--test)
|
--test)
|
||||||
TEST_AFTER=true
|
TEST_AFTER=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--dry-run)
|
--dry-run)
|
||||||
DRY_RUN=true
|
DRY_RUN=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown option: $1"
|
log_error "Unknown option: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check if Thorium is installed
|
# Check if Thorium is installed
|
||||||
check_thorium_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"
|
log_error "thorium-browser not found in PATH"
|
||||||
echo -e "${YELLOW}Install with: yay -S thorium-browser-bin${NC}"
|
echo -e "${YELLOW}Install with: yay -S thorium-browser-bin${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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 if config directory exists
|
||||||
check_config_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_warn "Thorium config directory not found: $THORIUM_CONFIG_DIR"
|
||||||
log_info "This may be a fresh install - try running thorium-browser directly"
|
log_info "This may be a fresh install - try running thorium-browser directly"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Kill any running Thorium processes
|
# Kill any running Thorium processes
|
||||||
kill_thorium() {
|
kill_thorium() {
|
||||||
local count
|
local count
|
||||||
count=$(pgrep -c thorium 2>/dev/null || true)
|
count=$(pgrep -c thorium 2> /dev/null || true)
|
||||||
count=${count:-0}
|
count=${count:-0}
|
||||||
|
|
||||||
if [[ $count -gt 0 ]]; then
|
if [[ $count -gt 0 ]]; then
|
||||||
log_info "Stopping $count running Thorium process(es)..."
|
log_info "Stopping $count running Thorium process(es)..."
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo " [dry-run] Would kill thorium processes"
|
echo " [dry-run] Would kill thorium processes"
|
||||||
else
|
else
|
||||||
pkill -9 thorium 2>/dev/null || true
|
pkill -9 thorium 2> /dev/null || true
|
||||||
sleep 1
|
sleep 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Backup a file/directory if it exists
|
# Backup a file/directory if it exists
|
||||||
backup_if_exists() {
|
backup_if_exists() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local name
|
local name
|
||||||
name=$(basename "$path")
|
name=$(basename "$path")
|
||||||
|
|
||||||
if [[ -e "$path" ]]; then
|
if [[ -e $path ]]; then
|
||||||
local backup_path="${path}${BACKUP_SUFFIX}"
|
local backup_path="${path}${BACKUP_SUFFIX}"
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo " [dry-run] Would backup: $name"
|
echo " [dry-run] Would backup: $name"
|
||||||
else
|
else
|
||||||
mv "$path" "$backup_path"
|
mv "$path" "$backup_path"
|
||||||
log_ok "Backed up: $name -> $(basename "$backup_path")"
|
log_ok "Backed up: $name -> $(basename "$backup_path")"
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove file/directory if it exists
|
# Remove file/directory if it exists
|
||||||
remove_if_exists() {
|
remove_if_exists() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local name
|
local name
|
||||||
name=$(basename "$path")
|
name=$(basename "$path")
|
||||||
|
|
||||||
if [[ -e "$path" ]]; then
|
if [[ -e $path ]]; then
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo " [dry-run] Would remove: $name"
|
echo " [dry-run] Would remove: $name"
|
||||||
else
|
else
|
||||||
rm -rf "$path"
|
rm -rf "$path"
|
||||||
log_ok "Removed: $name"
|
log_ok "Removed: $name"
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fix 1: Handle corrupted Local State file (most common crash cause)
|
# Fix 1: Handle corrupted Local State file (most common crash cause)
|
||||||
fix_local_state() {
|
fix_local_state() {
|
||||||
log_info "Checking Local State file..."
|
log_info "Checking Local State file..."
|
||||||
local local_state="$THORIUM_CONFIG_DIR/Local State"
|
local local_state="$THORIUM_CONFIG_DIR/Local State"
|
||||||
|
|
||||||
if [[ -f "$local_state" ]]; then
|
if [[ -f $local_state ]]; then
|
||||||
# Check if it's valid JSON
|
# 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"
|
log_warn "Local State file appears corrupted"
|
||||||
backup_if_exists "$local_state"
|
backup_if_exists "$local_state"
|
||||||
else
|
else
|
||||||
# Even if valid JSON, back it up as it can still cause crashes
|
# Even if valid JSON, back it up as it can still cause crashes
|
||||||
log_info "Local State exists - backing up (common crash source)"
|
log_info "Local State exists - backing up (common crash source)"
|
||||||
backup_if_exists "$local_state"
|
backup_if_exists "$local_state"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_info "No Local State file found (OK for fresh install)"
|
log_info "No Local State file found (OK for fresh install)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fix 2: Clear singleton lock files
|
# Fix 2: Clear singleton lock files
|
||||||
fix_singleton_locks() {
|
fix_singleton_locks() {
|
||||||
log_info "Clearing singleton lock files..."
|
log_info "Clearing singleton lock files..."
|
||||||
local locks=(
|
local locks=(
|
||||||
"$THORIUM_CONFIG_DIR/SingletonLock"
|
"$THORIUM_CONFIG_DIR/SingletonLock"
|
||||||
"$THORIUM_CONFIG_DIR/SingletonSocket"
|
"$THORIUM_CONFIG_DIR/SingletonSocket"
|
||||||
"$THORIUM_CONFIG_DIR/SingletonCookie"
|
"$THORIUM_CONFIG_DIR/SingletonCookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
local cleared=0
|
local cleared=0
|
||||||
for lock in "${locks[@]}"; do
|
for lock in "${locks[@]}"; do
|
||||||
if remove_if_exists "$lock"; then
|
if remove_if_exists "$lock"; then
|
||||||
((cleared++)) || true
|
((cleared++)) || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $cleared -eq 0 ]]; then
|
if [[ $cleared -eq 0 ]]; then
|
||||||
log_info "No stale lock files found"
|
log_info "No stale lock files found"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fix 3: Clear GPU cache
|
# Fix 3: Clear GPU cache
|
||||||
fix_gpu_cache() {
|
fix_gpu_cache() {
|
||||||
log_info "Clearing GPU cache..."
|
log_info "Clearing GPU cache..."
|
||||||
local gpu_paths=(
|
local gpu_paths=(
|
||||||
"$THORIUM_CONFIG_DIR/GPUCache"
|
"$THORIUM_CONFIG_DIR/GPUCache"
|
||||||
"$THORIUM_CONFIG_DIR/Default/GPUCache"
|
"$THORIUM_CONFIG_DIR/Default/GPUCache"
|
||||||
"$THORIUM_CONFIG_DIR/ShaderCache"
|
"$THORIUM_CONFIG_DIR/ShaderCache"
|
||||||
"$THORIUM_CONFIG_DIR/Default/ShaderCache"
|
"$THORIUM_CONFIG_DIR/Default/ShaderCache"
|
||||||
)
|
)
|
||||||
|
|
||||||
local cleared=0
|
local cleared=0
|
||||||
for cache in "${gpu_paths[@]}"; do
|
for cache in "${gpu_paths[@]}"; do
|
||||||
if remove_if_exists "$cache"; then
|
if remove_if_exists "$cache"; then
|
||||||
((cleared++)) || true
|
((cleared++)) || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $cleared -eq 0 ]]; then
|
if [[ $cleared -eq 0 ]]; then
|
||||||
log_info "No GPU cache to clear"
|
log_info "No GPU cache to clear"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fix 4: Clear crash reports (can accumulate and cause issues)
|
# Fix 4: Clear crash reports (can accumulate and cause issues)
|
||||||
fix_crash_reports() {
|
fix_crash_reports() {
|
||||||
log_info "Clearing old crash reports..."
|
log_info "Clearing old crash reports..."
|
||||||
local crash_dir="$THORIUM_CONFIG_DIR/Crash Reports"
|
local crash_dir="$THORIUM_CONFIG_DIR/Crash Reports"
|
||||||
|
|
||||||
if [[ -d "$crash_dir" ]]; then
|
if [[ -d $crash_dir ]]; then
|
||||||
local crash_count
|
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 [[ $crash_count -gt 0 ]]; then
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo " [dry-run] Would clear $crash_count crash report(s)"
|
echo " [dry-run] Would clear $crash_count crash report(s)"
|
||||||
else
|
else
|
||||||
rm -rf "$crash_dir"
|
rm -rf "$crash_dir"
|
||||||
log_ok "Cleared $crash_count crash report(s)"
|
log_ok "Cleared $crash_count crash report(s)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fix 5: Aggressive cleaning (optional)
|
# Fix 5: Aggressive cleaning (optional)
|
||||||
fix_aggressive() {
|
fix_aggressive() {
|
||||||
if [[ $AGGRESSIVE != true ]]; then
|
if [[ $AGGRESSIVE != true ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_warn "Applying aggressive fixes (may lose some site data)..."
|
log_warn "Applying aggressive fixes (may lose some site data)..."
|
||||||
|
|
||||||
local aggressive_paths=(
|
local aggressive_paths=(
|
||||||
"$THORIUM_CONFIG_DIR/Default/Service Worker"
|
"$THORIUM_CONFIG_DIR/Default/Service Worker"
|
||||||
"$THORIUM_CONFIG_DIR/Default/Cache"
|
"$THORIUM_CONFIG_DIR/Default/Cache"
|
||||||
"$THORIUM_CONFIG_DIR/Default/Code Cache"
|
"$THORIUM_CONFIG_DIR/Default/Code Cache"
|
||||||
"$THORIUM_CONFIG_DIR/Default/IndexedDB"
|
"$THORIUM_CONFIG_DIR/Default/IndexedDB"
|
||||||
"$THORIUM_CONFIG_DIR/BrowserMetrics"
|
"$THORIUM_CONFIG_DIR/BrowserMetrics"
|
||||||
"$THORIUM_CONFIG_DIR/component_crx_cache"
|
"$THORIUM_CONFIG_DIR/component_crx_cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
for path in "${aggressive_paths[@]}"; do
|
for path in "${aggressive_paths[@]}"; do
|
||||||
remove_if_exists "$path"
|
remove_if_exists "$path"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Backup potentially corrupted databases
|
# Backup potentially corrupted databases
|
||||||
local db_files=(
|
local db_files=(
|
||||||
"$THORIUM_CONFIG_DIR/Default/Web Data"
|
"$THORIUM_CONFIG_DIR/Default/Web Data"
|
||||||
"$THORIUM_CONFIG_DIR/Default/History"
|
"$THORIUM_CONFIG_DIR/Default/History"
|
||||||
)
|
)
|
||||||
|
|
||||||
for db in "${db_files[@]}"; do
|
for db in "${db_files[@]}"; do
|
||||||
if [[ -f "$db" ]]; then
|
if [[ -f $db ]]; then
|
||||||
log_info "Checking database: $(basename "$db")"
|
log_info "Checking database: $(basename "$db")"
|
||||||
# Simple corruption check - if sqlite3 can't open it, back it up
|
# Simple corruption check - if sqlite3 can't open it, back it up
|
||||||
if command -v sqlite3 &>/dev/null; then
|
if command -v sqlite3 &> /dev/null; then
|
||||||
if ! sqlite3 "$db" "PRAGMA integrity_check;" &>/dev/null; then
|
if ! sqlite3 "$db" "PRAGMA integrity_check;" &> /dev/null; then
|
||||||
log_warn "Database may be corrupted: $(basename "$db")"
|
log_warn "Database may be corrupted: $(basename "$db")"
|
||||||
backup_if_exists "$db"
|
backup_if_exists "$db"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test if Thorium starts successfully
|
# Test if Thorium starts successfully
|
||||||
test_thorium() {
|
test_thorium() {
|
||||||
if [[ $TEST_AFTER != true ]]; then
|
if [[ $TEST_AFTER != true ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Testing Thorium startup..."
|
log_info "Testing Thorium startup..."
|
||||||
|
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo " [dry-run] Would test thorium-browser startup"
|
echo " [dry-run] Would test thorium-browser startup"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start Thorium in background
|
# Start Thorium in background
|
||||||
thorium-browser &>/dev/null &
|
thorium-browser &> /dev/null &
|
||||||
local pid=$!
|
local pid=$!
|
||||||
|
|
||||||
# Wait a few seconds and check if it's still running
|
# Wait a few seconds and check if it's still running
|
||||||
sleep 4
|
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)"
|
log_ok "Thorium started successfully! (PID: $pid)"
|
||||||
echo -e "${GREEN}Fix successful!${NC} Thorium is now running."
|
echo -e "${GREEN}Fix successful!${NC} Thorium is now running."
|
||||||
|
|
||||||
# Offer to keep it running or kill it
|
# Offer to keep it running or kill it
|
||||||
read -r -p "Keep browser running? [Y/n] " response
|
read -r -p "Keep browser running? [Y/n] " response
|
||||||
case "$response" in
|
case "$response" in
|
||||||
[nN]*)
|
[nN]*)
|
||||||
kill "$pid" 2>/dev/null || true
|
kill "$pid" 2> /dev/null || true
|
||||||
log_info "Browser closed"
|
log_info "Browser closed"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_info "Browser left running"
|
log_info "Browser left running"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
else
|
else
|
||||||
log_error "Thorium still crashing after fixes"
|
log_error "Thorium still crashing after fixes"
|
||||||
echo -e "${RED}Standard fixes did not resolve the issue.${NC}"
|
echo -e "${RED}Standard fixes did not resolve the issue.${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Try these additional steps:"
|
echo "Try these additional steps:"
|
||||||
echo " 1. Run with --aggressive flag for deeper cleaning"
|
echo " 1. Run with --aggressive flag for deeper cleaning"
|
||||||
echo " 2. Test with fresh profile: thorium-browser --user-data-dir=/tmp/thorium-test"
|
echo " 2. Test with fresh profile: thorium-browser --user-data-dir=/tmp/thorium-test"
|
||||||
echo " 3. Reinstall: yay -S thorium-browser-bin"
|
echo " 3. Reinstall: yay -S thorium-browser-bin"
|
||||||
echo " 4. Check NVIDIA drivers: nvidia-smi"
|
echo " 4. Check NVIDIA drivers: nvidia-smi"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
main() {
|
main() {
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo " Thorium Browser Fix Script"
|
echo " Thorium Browser Fix Script"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ $DRY_RUN == true ]]; then
|
if [[ $DRY_RUN == true ]]; then
|
||||||
echo -e "${YELLOW}[DRY RUN MODE - no changes will be made]${NC}"
|
echo -e "${YELLOW}[DRY RUN MODE - no changes will be made]${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
check_thorium_installed
|
check_thorium_installed
|
||||||
check_config_exists
|
check_config_exists
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Applying fixes to: $THORIUM_CONFIG_DIR"
|
log_info "Applying fixes to: $THORIUM_CONFIG_DIR"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
kill_thorium
|
kill_thorium
|
||||||
fix_local_state
|
fix_local_state
|
||||||
fix_singleton_locks
|
fix_singleton_locks
|
||||||
fix_gpu_cache
|
fix_gpu_cache
|
||||||
fix_crash_reports
|
fix_crash_reports
|
||||||
fix_aggressive
|
fix_aggressive
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
log_ok "Fixes applied!"
|
log_ok "Fixes applied!"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
if [[ $DRY_RUN != true ]]; then
|
if [[ $DRY_RUN != true ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Backups created with suffix: $BACKUP_SUFFIX"
|
echo "Backups created with suffix: $BACKUP_SUFFIX"
|
||||||
echo "To restore: mv ~/.config/thorium/Local\\ State${BACKUP_SUFFIX} ~/.config/thorium/Local\\ State"
|
echo "To restore: mv ~/.config/thorium/Local\\ State${BACKUP_SUFFIX} ~/.config/thorium/Local\\ State"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
test_thorium
|
test_thorium
|
||||||
|
|
||||||
if [[ $TEST_AFTER != true ]]; then
|
if [[ $TEST_AFTER != true ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Run 'thorium-browser' to test, or use: $(basename "$0") --test"
|
echo "Run 'thorium-browser' to test, or use: $(basename "$0") --test"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -8,176 +8,176 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|||||||
source "$SCRIPT_DIR/../lib/common.sh"
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
on_error() {
|
on_error() {
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
local line_number=$1
|
local line_number=$1
|
||||||
log_error "Unexpected failure at line ${line_number} (exit code ${exit_code})."
|
log_error "Unexpected failure at line ${line_number} (exit code ${exit_code})."
|
||||||
}
|
}
|
||||||
trap 'on_error ${LINENO}' ERR
|
trap 'on_error ${LINENO}' ERR
|
||||||
|
|
||||||
require_pacman() {
|
require_pacman() {
|
||||||
if ! has_cmd pacman; then
|
if ! has_cmd pacman; then
|
||||||
log_error "pacman not found. This script is intended for Arch Linux systems."
|
log_error "pacman not found. This script is intended for Arch Linux systems."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_kernel_release() {
|
detect_kernel_release() {
|
||||||
uname -r
|
uname -r
|
||||||
}
|
}
|
||||||
|
|
||||||
select_host_package() {
|
select_host_package() {
|
||||||
local kernel_release=$1
|
local kernel_release=$1
|
||||||
case "${kernel_release}" in
|
case "${kernel_release}" in
|
||||||
*-lts)
|
*-lts)
|
||||||
echo "virtualbox-host-modules-lts"
|
echo "virtualbox-host-modules-lts"
|
||||||
;;
|
;;
|
||||||
*-arch*)
|
*-arch*)
|
||||||
echo "virtualbox-host-modules-arch"
|
echo "virtualbox-host-modules-arch"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "virtualbox-host-dkms"
|
echo "virtualbox-host-dkms"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_kernel_headers() {
|
collect_kernel_headers() {
|
||||||
local -a headers=()
|
local -a headers=()
|
||||||
local kernel_pkg header_pkg
|
local kernel_pkg header_pkg
|
||||||
for kernel_pkg in linux linux-lts linux-zen linux-hardened; do
|
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"
|
header_pkg="${kernel_pkg}-headers"
|
||||||
headers+=("${header_pkg}")
|
headers+=("${header_pkg}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ ${#headers[@]} -gt 0 ]]; then
|
if [[ ${#headers[@]} -gt 0 ]]; then
|
||||||
printf '%s\n' "${headers[@]}"
|
printf '%s\n' "${headers[@]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_remove_conflicting_host_packages() {
|
maybe_remove_conflicting_host_packages() {
|
||||||
local selected_package=$1
|
local selected_package=$1
|
||||||
local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts")
|
local -a candidates=("virtualbox-host-dkms" "virtualbox-host-modules-arch" "virtualbox-host-modules-lts")
|
||||||
local pkg
|
local pkg
|
||||||
for pkg in "${candidates[@]}"; do
|
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}."
|
log_warn "Removing conflicting package ${pkg} before installing ${selected_package}."
|
||||||
pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}"
|
pacman -Rsn "${PACMAN_REMOVE_FLAGS[@]}" "${pkg}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
install_packages() {
|
install_packages() {
|
||||||
local -a packages=()
|
local -a packages=()
|
||||||
local -a headers=()
|
local -a headers=()
|
||||||
local host_package=$1
|
local host_package=$1
|
||||||
shift
|
shift
|
||||||
if [[ $# -gt 0 ]]; then
|
if [[ $# -gt 0 ]]; then
|
||||||
mapfile -t headers < <(printf '%s\n' "$@" | sort -u)
|
mapfile -t headers < <(printf '%s\n' "$@" | sort -u)
|
||||||
fi
|
fi
|
||||||
packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}")
|
packages+=("virtualbox" "virtualbox-guest-iso" "${host_package}")
|
||||||
if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
|
if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
|
||||||
packages+=("dkms")
|
packages+=("dkms")
|
||||||
fi
|
fi
|
||||||
if [[ ${#headers[@]} -gt 0 ]]; then
|
if [[ ${#headers[@]} -gt 0 ]]; then
|
||||||
packages+=("${headers[@]}")
|
packages+=("${headers[@]}")
|
||||||
fi
|
fi
|
||||||
log_info "Installing packages: ${packages[*]}"
|
log_info "Installing packages: ${packages[*]}"
|
||||||
pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}"
|
pacman -S "${PACMAN_INSTALL_FLAGS[@]}" "${packages[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuild_virtualbox_modules() {
|
rebuild_virtualbox_modules() {
|
||||||
local host_package=$1
|
local host_package=$1
|
||||||
if [[ ${host_package} == "virtualbox-host-dkms" ]]; then
|
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."
|
log_info "Rebuilding VirtualBox DKMS modules for all installed kernels."
|
||||||
dkms autoinstall
|
dkms autoinstall
|
||||||
else
|
else
|
||||||
log_warn "dkms command not found; skipping DKMS rebuild."
|
log_warn "dkms command not found; skipping DKMS rebuild."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
reload_virtualbox_modules() {
|
reload_virtualbox_modules() {
|
||||||
log_info "Loading VirtualBox kernel modules."
|
log_info "Loading VirtualBox kernel modules."
|
||||||
if [[ -x /sbin/rcvboxdrv ]]; then
|
if [[ -x /sbin/rcvboxdrv ]]; then
|
||||||
/sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules."
|
/sbin/rcvboxdrv setup || log_warn "rcvboxdrv reported an issue while setting up modules."
|
||||||
elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then
|
elif [[ -x /usr/lib/virtualbox/vboxdrv.sh ]]; then
|
||||||
/usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules."
|
/usr/lib/virtualbox/vboxdrv.sh setup || log_warn "vboxdrv.sh reported an issue while setting up modules."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci)
|
local -a modules=(vboxdrv vboxnetflt vboxnetadp vboxpci)
|
||||||
local mod
|
local mod
|
||||||
for mod in "${modules[@]}"; do
|
for mod in "${modules[@]}"; do
|
||||||
if ! lsmod | awk '{print $1}' | grep -Fxq "${mod}"; then
|
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."
|
log_warn "Module ${mod} failed to load; check dmesg for details."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then
|
if ! lsmod | awk '{print $1}' | grep -Fxq "vboxdrv"; then
|
||||||
log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues."
|
log_error "VirtualBox kernel driver (vboxdrv) failed to load. Review /var/log and dmesg output for clues."
|
||||||
fi
|
fi
|
||||||
log_info "VirtualBox kernel driver loaded successfully."
|
log_info "VirtualBox kernel driver loaded successfully."
|
||||||
}
|
}
|
||||||
|
|
||||||
warn_if_secure_boot_enabled() {
|
warn_if_secure_boot_enabled() {
|
||||||
local secure_boot_file
|
local secure_boot_file
|
||||||
if [[ -d /sys/firmware/efi/efivars ]]; then
|
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
|
if [[ -n ${secure_boot_file} && -r ${secure_boot_file} ]]; then
|
||||||
local state
|
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
|
if [[ ${state} == "1" ]]; then
|
||||||
log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually."
|
log_warn "EFI Secure Boot appears to be enabled. You may need to sign VirtualBox modules manually."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
remind_group_membership() {
|
remind_group_membership() {
|
||||||
local invoking_user=${SUDO_USER:-}
|
local invoking_user=${SUDO_USER:-}
|
||||||
if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then
|
if [[ -n ${invoking_user} && ${invoking_user} != "root" ]]; then
|
||||||
if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then
|
if ! id -nG "${invoking_user}" | grep -qw "vboxusers"; then
|
||||||
log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers"
|
log_warn "User ${invoking_user} is not in the vboxusers group. Add them with: sudo gpasswd -a ${invoking_user} vboxusers"
|
||||||
else
|
else
|
||||||
log_info "User ${invoking_user} is already in the vboxusers group."
|
log_info "User ${invoking_user} is already in the vboxusers group."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
require_root
|
require_root
|
||||||
require_pacman
|
require_pacman
|
||||||
|
|
||||||
PACMAN_INSTALL_FLAGS=(--needed)
|
PACMAN_INSTALL_FLAGS=(--needed)
|
||||||
PACMAN_REMOVE_FLAGS=()
|
PACMAN_REMOVE_FLAGS=()
|
||||||
if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then
|
if [[ ${PACMAN_CONFIRM:-0} == "1" ]]; then
|
||||||
log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation."
|
log_info "PACMAN_CONFIRM=1 detected; pacman will prompt for confirmation."
|
||||||
else
|
else
|
||||||
PACMAN_INSTALL_FLAGS+=(--noconfirm)
|
PACMAN_INSTALL_FLAGS+=(--noconfirm)
|
||||||
PACMAN_REMOVE_FLAGS+=(--noconfirm)
|
PACMAN_REMOVE_FLAGS+=(--noconfirm)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local kernel_release host_package
|
local kernel_release host_package
|
||||||
kernel_release=$(detect_kernel_release)
|
kernel_release=$(detect_kernel_release)
|
||||||
log_info "Detected running kernel: ${kernel_release}"
|
log_info "Detected running kernel: ${kernel_release}"
|
||||||
host_package=$(select_host_package "${kernel_release}")
|
host_package=$(select_host_package "${kernel_release}")
|
||||||
log_info "Selected VirtualBox host package: ${host_package}"
|
log_info "Selected VirtualBox host package: ${host_package}"
|
||||||
|
|
||||||
mapfile -t kernel_headers < <(collect_kernel_headers)
|
mapfile -t kernel_headers < <(collect_kernel_headers)
|
||||||
if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then
|
if [[ ${host_package} == "virtualbox-host-dkms" && ${#kernel_headers[@]} -eq 0 ]]; then
|
||||||
log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules."
|
log_warn "No matching kernel headers detected. Ensure you've installed headers for your kernel so DKMS can build modules."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
maybe_remove_conflicting_host_packages "${host_package}"
|
maybe_remove_conflicting_host_packages "${host_package}"
|
||||||
install_packages "${host_package}" "${kernel_headers[@]}"
|
install_packages "${host_package}" "${kernel_headers[@]}"
|
||||||
rebuild_virtualbox_modules "${host_package}"
|
rebuild_virtualbox_modules "${host_package}"
|
||||||
reload_virtualbox_modules
|
reload_virtualbox_modules
|
||||||
warn_if_secure_boot_enabled
|
warn_if_secure_boot_enabled
|
||||||
remind_group_membership
|
remind_group_membership
|
||||||
|
|
||||||
log_info "VirtualBox installation and driver setup complete."
|
log_info "VirtualBox installation and driver setup complete."
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -8,57 +8,57 @@ set -euo pipefail
|
|||||||
echo "=== Fixing yay AUR database ==="
|
echo "=== Fixing yay AUR database ==="
|
||||||
|
|
||||||
# Check if using yay-git (development version with potential bugs)
|
# 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 ""
|
||||||
echo "Detected yay-git (development version)."
|
echo "Detected yay-git (development version)."
|
||||||
echo "The 'database AUR not found' error is a known bug in some yay-git versions."
|
echo "The 'database AUR not found' error is a known bug in some yay-git versions."
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Switch to stable yay? [Y/n] " response
|
read -rp "Switch to stable yay? [Y/n] " response
|
||||||
if [[ "${response,,}" != "n" ]]; then
|
if [[ ${response,,} != "n" ]]; then
|
||||||
echo "Switching to stable yay..."
|
echo "Switching to stable yay..."
|
||||||
|
|
||||||
# Build and install stable yay from AUR
|
# Build and install stable yay from AUR
|
||||||
TEMP_DIR=$(mktemp -d)
|
TEMP_DIR=$(mktemp -d)
|
||||||
cd "$TEMP_DIR"
|
cd "$TEMP_DIR"
|
||||||
git clone https://aur.archlinux.org/yay.git
|
git clone https://aur.archlinux.org/yay.git
|
||||||
cd yay
|
cd yay
|
||||||
|
|
||||||
# Remove yay-git and yay-git-debug (they conflict)
|
# Remove yay-git and yay-git-debug (they conflict)
|
||||||
sudo pacman -Rdd yay-git --noconfirm
|
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
|
# Build and install stable yay
|
||||||
makepkg -si --noconfirm
|
makepkg -si --noconfirm
|
||||||
|
|
||||||
cd /
|
cd /
|
||||||
rm -rf "$TEMP_DIR"
|
rm -rf "$TEMP_DIR"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Switched to stable yay ==="
|
echo "=== Switched to stable yay ==="
|
||||||
echo "You can now retry your yay command."
|
echo "You can now retry your yay command."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove yay's cache directory
|
# Remove yay's cache directory
|
||||||
YAY_CACHE_DIR="${HOME}/.cache/yay"
|
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"
|
echo "Removing yay cache directory: $YAY_CACHE_DIR"
|
||||||
rm -rf "$YAY_CACHE_DIR"
|
rm -rf "$YAY_CACHE_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove yay's local database directory (stores AUR package info)
|
# Remove yay's local database directory (stores AUR package info)
|
||||||
YAY_DB_DIR="${HOME}/.local/share/yay"
|
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"
|
echo "Removing yay database directory: $YAY_DB_DIR"
|
||||||
rm -rf "$YAY_DB_DIR"
|
rm -rf "$YAY_DB_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove yay state directory
|
# Remove yay state directory
|
||||||
YAY_STATE_DIR="${HOME}/.local/state/yay"
|
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"
|
echo "Removing yay state directory: $YAY_STATE_DIR"
|
||||||
rm -rf "$YAY_STATE_DIR"
|
rm -rf "$YAY_STATE_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clear pacman's sync databases and refresh
|
# Clear pacman's sync databases and refresh
|
||||||
|
|||||||
@ -21,7 +21,7 @@ print_setup_header "NVIDIA Comprehensive Troubleshooter & GSP Disabler"
|
|||||||
|
|
||||||
# Check if nvidia module is loaded
|
# Check if nvidia module is loaded
|
||||||
if ! lsmod | grep -q nvidia; then
|
if ! lsmod | grep -q nvidia; then
|
||||||
echo "Warning: NVIDIA module not currently loaded"
|
echo "Warning: NVIDIA module not currently loaded"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create modprobe configuration directory if it doesn't exist
|
# Create modprobe configuration directory if it doesn't exist
|
||||||
@ -34,7 +34,7 @@ echo "======================================"
|
|||||||
mkdir -p "$MODPROBE_DIR"
|
mkdir -p "$MODPROBE_DIR"
|
||||||
|
|
||||||
# Create the configuration file
|
# Create the configuration file
|
||||||
cat >"$CONFIG_FILE" <<EOF
|
cat > "$CONFIG_FILE" << EOF
|
||||||
# Disable NVIDIA GSP firmware to prevent Vulkan failures and crashes
|
# Disable NVIDIA GSP firmware to prevent Vulkan failures and crashes
|
||||||
# Created by nvidia_troubleshoot.sh on $(date)
|
# Created by nvidia_troubleshoot.sh on $(date)
|
||||||
options nvidia NVreg_EnableGpuFirmware=0
|
options nvidia NVreg_EnableGpuFirmware=0
|
||||||
@ -44,32 +44,32 @@ echo "✓ Configuration written to: $CONFIG_FILE"
|
|||||||
|
|
||||||
# Function to backup file if it exists
|
# Function to backup file if it exists
|
||||||
backup_file() {
|
backup_file() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
if [[ -f $file ]]; then
|
if [[ -f $file ]]; then
|
||||||
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
|
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
echo "✓ Backed up $file"
|
echo "✓ Backed up $file"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to add or update xorg.conf for RenderAccel
|
# Function to add or update xorg.conf for RenderAccel
|
||||||
configure_xorg() {
|
configure_xorg() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Configuring Xorg Settings..."
|
echo "2. Configuring Xorg Settings..."
|
||||||
echo "==============================="
|
echo "==============================="
|
||||||
|
|
||||||
XORG_CONF="/etc/X11/xorg.conf"
|
XORG_CONF="/etc/X11/xorg.conf"
|
||||||
XORG_CONF_D="/etc/X11/xorg.conf.d"
|
XORG_CONF_D="/etc/X11/xorg.conf.d"
|
||||||
NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf"
|
NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf"
|
||||||
|
|
||||||
# Create xorg.conf.d directory if it doesn't exist
|
# Create xorg.conf.d directory if it doesn't exist
|
||||||
mkdir -p "$XORG_CONF_D"
|
mkdir -p "$XORG_CONF_D"
|
||||||
|
|
||||||
# Backup existing xorg.conf if it exists
|
# Backup existing xorg.conf if it exists
|
||||||
backup_file "$XORG_CONF"
|
backup_file "$XORG_CONF"
|
||||||
backup_file "$NVIDIA_CONF"
|
backup_file "$NVIDIA_CONF"
|
||||||
|
|
||||||
# Create NVIDIA-specific configuration
|
# Create NVIDIA-specific configuration
|
||||||
cat >"$NVIDIA_CONF" <<EOF
|
cat > "$NVIDIA_CONF" << EOF
|
||||||
# NVIDIA configuration with RenderAccel disabled
|
# NVIDIA configuration with RenderAccel disabled
|
||||||
# Created by nvidia_troubleshoot.sh on $(date)
|
# Created by nvidia_troubleshoot.sh on $(date)
|
||||||
Section "Device"
|
Section "Device"
|
||||||
@ -79,106 +79,106 @@ Section "Device"
|
|||||||
EndSection
|
EndSection
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created $NVIDIA_CONF with RenderAccel disabled"
|
echo "✓ Created $NVIDIA_CONF with RenderAccel disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to add GCC mismatch workaround
|
# Function to add GCC mismatch workaround
|
||||||
configure_gcc_workaround() {
|
configure_gcc_workaround() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Configuring GCC Mismatch Workaround..."
|
echo "3. Configuring GCC Mismatch Workaround..."
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|
||||||
local PROFILE_FILE="/etc/profile"
|
local PROFILE_FILE="/etc/profile"
|
||||||
local timestamp
|
local timestamp
|
||||||
timestamp=$(date)
|
timestamp=$(date)
|
||||||
backup_file "$PROFILE_FILE"
|
backup_file "$PROFILE_FILE"
|
||||||
|
|
||||||
# Check if IGNORE_CC_MISMATCH is already set
|
# Check if IGNORE_CC_MISMATCH is already set
|
||||||
if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then
|
if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then
|
||||||
{
|
{
|
||||||
printf '\n'
|
printf '\n'
|
||||||
printf '# NVIDIA GCC version mismatch workaround\n'
|
printf '# NVIDIA GCC version mismatch workaround\n'
|
||||||
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
|
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
|
||||||
printf 'export IGNORE_CC_MISMATCH=1\n'
|
printf 'export IGNORE_CC_MISMATCH=1\n'
|
||||||
} >>"$PROFILE_FILE"
|
} >> "$PROFILE_FILE"
|
||||||
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
|
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
|
||||||
else
|
else
|
||||||
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
|
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to install pyroveil for mesh shader issues
|
# Function to install pyroveil for mesh shader issues
|
||||||
install_pyroveil() {
|
install_pyroveil() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Pyroveil Setup for Mesh Shader Issues..."
|
echo "4. Pyroveil Setup for Mesh Shader Issues..."
|
||||||
echo "==========================================="
|
echo "==========================================="
|
||||||
|
|
||||||
local user_home="/home/$SUDO_USER"
|
local user_home="/home/$SUDO_USER"
|
||||||
local pyroveil_dir="$user_home/pyroveil"
|
local pyroveil_dir="$user_home/pyroveil"
|
||||||
|
|
||||||
echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games"
|
echo "Mesh shaders have poor support on NVIDIA drivers, causing issues in games"
|
||||||
echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems."
|
echo "like Final Fantasy VII Rebirth. Pyroveil can work around these problems."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local install_pyroveil=true
|
local install_pyroveil=true
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
|
read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
install_pyroveil=false
|
install_pyroveil=false
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Auto-installing Pyroveil (use --interactive to prompt)"
|
echo "Auto-installing Pyroveil (use --interactive to prompt)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $install_pyroveil == "true" ]]; then
|
if [[ $install_pyroveil == "true" ]]; then
|
||||||
# Check for required dependencies
|
# Check for required dependencies
|
||||||
local missing_deps=()
|
local missing_deps=()
|
||||||
|
|
||||||
for dep in git cmake ninja gcc; do
|
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")
|
missing_deps+=("$dep")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
||||||
echo "Missing dependencies: ${missing_deps[*]}"
|
echo "Missing dependencies: ${missing_deps[*]}"
|
||||||
echo "Please install them first. On Arch Linux:"
|
echo "Please install them first. On Arch Linux:"
|
||||||
echo "pacman -S base-devel git cmake ninja"
|
echo "pacman -S base-devel git cmake ninja"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clone and build pyroveil as the original user
|
# Clone and build pyroveil as the original user
|
||||||
echo "Installing Pyroveil to $pyroveil_dir..."
|
echo "Installing Pyroveil to $pyroveil_dir..."
|
||||||
|
|
||||||
if [[ -d $pyroveil_dir ]]; then
|
if [[ -d $pyroveil_dir ]]; then
|
||||||
echo "Pyroveil directory already exists. Updating..."
|
echo "Pyroveil directory already exists. Updating..."
|
||||||
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
|
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
|
||||||
else
|
else
|
||||||
sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
|
sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sudo -u "$SUDO_USER" bash -c "
|
sudo -u "$SUDO_USER" bash -c "
|
||||||
cd '$pyroveil_dir'
|
cd '$pyroveil_dir'
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local
|
cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local
|
||||||
ninja -C build install
|
ninja -C build install
|
||||||
"
|
"
|
||||||
|
|
||||||
echo "✓ Pyroveil installed successfully"
|
echo "✓ Pyroveil installed successfully"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To use Pyroveil with games that have mesh shader issues:"
|
echo "To use Pyroveil with games that have mesh shader issues:"
|
||||||
echo "1. For Final Fantasy VII Rebirth:"
|
echo "1. For Final Fantasy VII Rebirth:"
|
||||||
echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%"
|
echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%"
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. For Steam games, add to launch options:"
|
echo "2. For Steam games, add to launch options:"
|
||||||
echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%"
|
echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Available configs in: $pyroveil_dir/hacks/"
|
echo "Available configs in: $pyroveil_dir/hacks/"
|
||||||
|
|
||||||
# Create a helper script
|
# Create a helper script
|
||||||
cat >"$user_home/run-with-pyroveil.sh" <<EOF
|
cat > "$user_home/run-with-pyroveil.sh" << EOF
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Helper script to run games with Pyroveil
|
# Helper script to run games with Pyroveil
|
||||||
# Usage: ./run-with-pyroveil.sh <config-name> <command>
|
# Usage: ./run-with-pyroveil.sh <config-name> <command>
|
||||||
@ -204,88 +204,88 @@ echo "Config file: \$PYROVEIL_CONFIG"
|
|||||||
exec "\$@"
|
exec "\$@"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh"
|
chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh"
|
||||||
chmod +x "$user_home/run-with-pyroveil.sh"
|
chmod +x "$user_home/run-with-pyroveil.sh"
|
||||||
echo "✓ Created helper script: $user_home/run-with-pyroveil.sh"
|
echo "✓ Created helper script: $user_home/run-with-pyroveil.sh"
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "Skipping Pyroveil installation"
|
echo "Skipping Pyroveil installation"
|
||||||
echo "Note: You can manually install it later for mesh shader issues"
|
echo "Note: You can manually install it later for mesh shader issues"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check for kernel parameter modifications
|
# Function to check for kernel parameter modifications
|
||||||
suggest_kernel_params() {
|
suggest_kernel_params() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "5. Kernel Parameter Recommendations..."
|
echo "5. Kernel Parameter Recommendations..."
|
||||||
echo "====================================="
|
echo "====================================="
|
||||||
|
|
||||||
echo "NVIDIA Driver Issues and Recommended Kernel Parameters:"
|
echo "NVIDIA Driver Issues and Recommended Kernel Parameters:"
|
||||||
echo ""
|
echo ""
|
||||||
echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors"
|
echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors"
|
||||||
echo " (especially with nvidia-96xx drivers):"
|
echo " (especially with nvidia-96xx drivers):"
|
||||||
echo " → Add 'nopat' to kernel parameters"
|
echo " → Add 'nopat' to kernel parameters"
|
||||||
echo ""
|
echo ""
|
||||||
echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:"
|
echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:"
|
||||||
echo " → Consider disabling micro-op cache in BIOS settings"
|
echo " → Consider disabling micro-op cache in BIOS settings"
|
||||||
echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs"
|
echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs"
|
||||||
echo " → Helps with severe graphical glitches in Xwayland applications"
|
echo " → Helps with severe graphical glitches in Xwayland applications"
|
||||||
echo " → Note: Disabling micro-op cache reduces CPU performance"
|
echo " → Note: Disabling micro-op cache reduces CPU performance"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To add kernel parameters:"
|
echo "To add kernel parameters:"
|
||||||
echo "1. Edit /etc/default/grub"
|
echo "1. Edit /etc/default/grub"
|
||||||
echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT"
|
echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT"
|
||||||
echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg"
|
echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg"
|
||||||
echo "4. Reboot"
|
echo "4. Reboot"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:"
|
echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:"
|
||||||
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"'
|
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"'
|
||||||
|
|
||||||
# Check current CPU for micro-op cache relevance
|
# Check current CPU for micro-op cache relevance
|
||||||
echo ""
|
echo ""
|
||||||
echo "CPU Information (for micro-op cache consideration):"
|
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
|
local cpu_info
|
||||||
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
|
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
|
||||||
echo "Current CPU: $cpu_info"
|
echo "Current CPU: $cpu_info"
|
||||||
|
|
||||||
if echo "$cpu_info" | grep -qi "intel"; then
|
if echo "$cpu_info" | grep -qi "intel"; then
|
||||||
echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache"
|
echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache"
|
||||||
elif echo "$cpu_info" | grep -qi "amd"; then
|
elif echo "$cpu_info" | grep -qi "amd"; then
|
||||||
echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache"
|
echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to suggest desktop environment settings
|
# Function to suggest desktop environment settings
|
||||||
suggest_desktop_settings() {
|
suggest_desktop_settings() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Desktop Environment Recommendations..."
|
echo "6. Desktop Environment Recommendations..."
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
echo "For fullscreen application freezing/crashing issues:"
|
echo "For fullscreen application freezing/crashing issues:"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Enable Display Compositing and Direct fullscreen rendering:"
|
echo "Enable Display Compositing and Direct fullscreen rendering:"
|
||||||
echo ""
|
echo ""
|
||||||
echo "• KDE Plasma:"
|
echo "• KDE Plasma:"
|
||||||
echo " System Settings → Display and Monitor → Compositor"
|
echo " System Settings → Display and Monitor → Compositor"
|
||||||
echo " → Enable compositor + Enable direct rendering for fullscreen windows"
|
echo " → Enable compositor + Enable direct rendering for fullscreen windows"
|
||||||
echo ""
|
echo ""
|
||||||
echo "• GNOME:"
|
echo "• GNOME:"
|
||||||
echo " Use Extensions or dconf-editor to enable compositing features"
|
echo " Use Extensions or dconf-editor to enable compositing features"
|
||||||
echo ""
|
echo ""
|
||||||
echo "• XFCE:"
|
echo "• XFCE:"
|
||||||
echo " Settings → Window Manager Tweaks → Compositor"
|
echo " Settings → Window Manager Tweaks → Compositor"
|
||||||
echo " → Enable display compositing"
|
echo " → Enable display compositing"
|
||||||
echo ""
|
echo ""
|
||||||
echo "• Cinnamon:"
|
echo "• Cinnamon:"
|
||||||
echo " System Settings → Effects → Enable desktop effects"
|
echo " System Settings → Effects → Enable desktop effects"
|
||||||
|
|
||||||
# Detect current desktop environment
|
# Detect current desktop environment
|
||||||
if [[ -n $XDG_CURRENT_DESKTOP ]]; then
|
if [[ -n $XDG_CURRENT_DESKTOP ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
|
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply all configurations
|
# Apply all configurations
|
||||||
@ -297,14 +297,14 @@ install_pyroveil
|
|||||||
echo ""
|
echo ""
|
||||||
echo "7. Regenerating Initramfs..."
|
echo "7. Regenerating Initramfs..."
|
||||||
echo "============================"
|
echo "============================"
|
||||||
if command -v mkinitcpio &>/dev/null; then
|
if command -v mkinitcpio &> /dev/null; then
|
||||||
mkinitcpio -P
|
mkinitcpio -P
|
||||||
echo "✓ Initramfs regenerated with mkinitcpio"
|
echo "✓ Initramfs regenerated with mkinitcpio"
|
||||||
elif command -v dracut &>/dev/null; then
|
elif command -v dracut &> /dev/null; then
|
||||||
dracut --force
|
dracut --force
|
||||||
echo "✓ Initramfs regenerated with dracut"
|
echo "✓ Initramfs regenerated with dracut"
|
||||||
else
|
else
|
||||||
echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs."
|
echo "Warning: Could not find mkinitcpio or dracut. You may need to manually regenerate initramfs."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display all recommendations
|
# Display all recommendations
|
||||||
@ -320,7 +320,7 @@ echo "✓ GSP firmware disabled"
|
|||||||
echo "✓ RenderAccel disabled in Xorg configuration"
|
echo "✓ RenderAccel disabled in Xorg configuration"
|
||||||
echo "✓ GCC version mismatch workaround added"
|
echo "✓ GCC version mismatch workaround added"
|
||||||
if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then
|
if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then
|
||||||
echo "✓ Pyroveil installed for mesh shader issues"
|
echo "✓ Pyroveil installed for mesh shader issues"
|
||||||
fi
|
fi
|
||||||
echo "✓ Initramfs regenerated"
|
echo "✓ Initramfs regenerated"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@ -11,49 +11,49 @@ ensure_dir "$ANDROID_WORK_DIR"
|
|||||||
|
|
||||||
# Exit with error message
|
# Exit with error message
|
||||||
die() {
|
die() {
|
||||||
echo "[ERROR] $*" >&2
|
echo "[ERROR] $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Print section header
|
# Print section header
|
||||||
print_header() {
|
print_header() {
|
||||||
echo
|
echo
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo " $1"
|
echo " $1"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize an Android script with common setup
|
# Initialize an Android script with common setup
|
||||||
# Usage: init_android_script "$@"
|
# Usage: init_android_script "$@"
|
||||||
# This combines: require_hosts_readable, sets WORK_DIR
|
# This combines: require_hosts_readable, sets WORK_DIR
|
||||||
init_android_script() {
|
init_android_script() {
|
||||||
require_hosts_readable "$@"
|
require_hosts_readable "$@"
|
||||||
WORK_DIR="$ANDROID_WORK_DIR"
|
WORK_DIR="$ANDROID_WORK_DIR"
|
||||||
export WORK_DIR
|
export WORK_DIR
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if ADB device is connected
|
# Check if ADB device is connected
|
||||||
check_adb_device() {
|
check_adb_device() {
|
||||||
log "Checking device connection..."
|
log "Checking device connection..."
|
||||||
if ! adb devices | grep -q "device$"; then
|
if ! adb devices | grep -q "device$"; then
|
||||||
die "No device connected. Enable USB debugging and connect your phone."
|
die "No device connected. Enable USB debugging and connect your phone."
|
||||||
fi
|
fi
|
||||||
log "Device connected"
|
log "Device connected"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if device has root access
|
# Check if device has root access
|
||||||
check_adb_root() {
|
check_adb_root() {
|
||||||
log "Checking root access..."
|
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."
|
die "Root access not available. Make sure Magisk is installed and grant root to Shell."
|
||||||
fi
|
fi
|
||||||
log "Root access confirmed"
|
log "Root access confirmed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Re-exec with sudo if needed to read /etc/hosts
|
# Re-exec with sudo if needed to read /etc/hosts
|
||||||
require_hosts_readable() {
|
require_hosts_readable() {
|
||||||
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
|
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
|
||||||
exec sudo -E bash "$0" "$@"
|
exec sudo -E bash "$0" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,20 +16,20 @@ _LIB_COMMON_LOADED=1
|
|||||||
# Log message with timestamp to stderr and optionally to a file
|
# Log message with timestamp to stderr and optionally to a file
|
||||||
# Usage: log_message "message" [log_file]
|
# Usage: log_message "message" [log_file]
|
||||||
log_message() {
|
log_message() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
local log_file="${2:-}"
|
local log_file="${2:-}"
|
||||||
local formatted
|
local formatted
|
||||||
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
|
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
|
||||||
echo "$formatted" >&2
|
echo "$formatted" >&2
|
||||||
if [[ -n $log_file ]]; then
|
if [[ -n $log_file ]]; then
|
||||||
echo "$formatted" >>"$log_file" 2>/dev/null || true
|
echo "$formatted" >> "$log_file" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Simple log with timestamp (no file output)
|
# Simple log with timestamp (no file output)
|
||||||
# Usage: log "message"
|
# Usage: log "message"
|
||||||
log() {
|
log() {
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -39,29 +39,29 @@ log() {
|
|||||||
# Check if running as root, if not re-exec with sudo
|
# Check if running as root, if not re-exec with sudo
|
||||||
# Usage: require_root "$@"
|
# Usage: require_root "$@"
|
||||||
require_root() {
|
require_root() {
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "This script requires root privileges."
|
echo "This script requires root privileges."
|
||||||
echo "Requesting sudo access..."
|
echo "Requesting sudo access..."
|
||||||
exec sudo "$0" "$@"
|
exec sudo "$0" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the actual user even when running with sudo
|
# Get the actual user even when running with sudo
|
||||||
# Usage: ACTUAL_USER=$(get_actual_user)
|
# Usage: ACTUAL_USER=$(get_actual_user)
|
||||||
get_actual_user() {
|
get_actual_user() {
|
||||||
echo "${SUDO_USER:-$USER}"
|
echo "${SUDO_USER:-$USER}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the actual user's home directory
|
# Get the actual user's home directory
|
||||||
# Usage: USER_HOME=$(get_actual_user_home)
|
# Usage: USER_HOME=$(get_actual_user_home)
|
||||||
get_actual_user_home() {
|
get_actual_user_home() {
|
||||||
local user
|
local user
|
||||||
user=$(get_actual_user)
|
user=$(get_actual_user)
|
||||||
if [[ $user == "root" ]]; then
|
if [[ $user == "root" ]]; then
|
||||||
echo "/root"
|
echo "/root"
|
||||||
else
|
else
|
||||||
echo "/home/$user"
|
echo "/home/$user"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set both ACTUAL_USER and USER_HOME variables (common pattern)
|
# Set both ACTUAL_USER and USER_HOME variables (common pattern)
|
||||||
@ -69,9 +69,9 @@ get_actual_user_home() {
|
|||||||
# echo "$ACTUAL_USER" # => the actual user
|
# echo "$ACTUAL_USER" # => the actual user
|
||||||
# echo "$USER_HOME" # => /home/username
|
# echo "$USER_HOME" # => /home/username
|
||||||
set_actual_user_vars() {
|
set_actual_user_vars() {
|
||||||
ACTUAL_USER=$(get_actual_user)
|
ACTUAL_USER=$(get_actual_user)
|
||||||
USER_HOME=$(get_actual_user_home)
|
USER_HOME=$(get_actual_user_home)
|
||||||
export ACTUAL_USER USER_HOME
|
export ACTUAL_USER USER_HOME
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -86,30 +86,30 @@ export INTERACTIVE_MODE=false
|
|||||||
export COMMON_ARGS_SHIFT=0
|
export COMMON_ARGS_SHIFT=0
|
||||||
|
|
||||||
parse_interactive_args() {
|
parse_interactive_args() {
|
||||||
INTERACTIVE_MODE=false
|
INTERACTIVE_MODE=false
|
||||||
COMMON_ARGS_SHIFT=0
|
COMMON_ARGS_SHIFT=0
|
||||||
local script_name="${0##*/}"
|
local script_name="${0##*/}"
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
-i | --interactive)
|
-i | --interactive)
|
||||||
INTERACTIVE_MODE=true
|
INTERACTIVE_MODE=true
|
||||||
((COMMON_ARGS_SHIFT++))
|
((COMMON_ARGS_SHIFT++))
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
echo "Usage: $script_name [OPTIONS]"
|
echo "Usage: $script_name [OPTIONS]"
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
|
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Stop parsing at first unknown argument
|
# Stop parsing at first unknown argument
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Handle common argument patterns for scripts with custom usage functions
|
# Handle common argument patterns for scripts with custom usage functions
|
||||||
@ -117,37 +117,37 @@ parse_interactive_args() {
|
|||||||
# Returns: 0 if argument was handled (caller should continue), 1 if not our concern
|
# Returns: 0 if argument was handled (caller should continue), 1 if not our concern
|
||||||
# Exits: on -h/--help (exit 0) or unknown arg starting with - (exit 2)
|
# Exits: on -h/--help (exit 0) or unknown arg starting with - (exit 2)
|
||||||
handle_arg_help_or_unknown() {
|
handle_arg_help_or_unknown() {
|
||||||
local arg="$1"
|
local arg="$1"
|
||||||
local usage_fn="${2:-usage}"
|
local usage_fn="${2:-usage}"
|
||||||
local err_fn="${3:-err}"
|
local err_fn="${3:-err}"
|
||||||
|
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
-h | --help)
|
-h | --help)
|
||||||
"$usage_fn"
|
"$usage_fn"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-*)
|
-*)
|
||||||
"$err_fn" "Unknown argument: $arg"
|
"$err_fn" "Unknown argument: $arg"
|
||||||
"$usage_fn"
|
"$usage_fn"
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
return 1 # Not a flag, let caller handle it
|
return 1 # Not a flag, let caller handle it
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize a setup script with common boilerplate
|
# Initialize a setup script with common boilerplate
|
||||||
# Usage: init_setup_script "Script Title" "$@"
|
# Usage: init_setup_script "Script Title" "$@"
|
||||||
# This combines: parse_interactive_args, shift, require_root, print_setup_header
|
# This combines: parse_interactive_args, shift, require_root, print_setup_header
|
||||||
init_setup_script() {
|
init_setup_script() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
shift
|
shift
|
||||||
parse_interactive_args "$@"
|
parse_interactive_args "$@"
|
||||||
shift "$COMMON_ARGS_SHIFT"
|
shift "$COMMON_ARGS_SHIFT"
|
||||||
require_root "$@"
|
require_root "$@"
|
||||||
print_setup_header "$title"
|
print_setup_header "$title"
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -156,51 +156,51 @@ init_setup_script() {
|
|||||||
|
|
||||||
# Default focus apps - can be overridden before calling is_focus_app_running
|
# Default focus apps - can be overridden before calling is_focus_app_running
|
||||||
FOCUS_APPS_WINDOWS=(
|
FOCUS_APPS_WINDOWS=(
|
||||||
"Visual Studio Code"
|
"Visual Studio Code"
|
||||||
"VSCodium"
|
"VSCodium"
|
||||||
"Cursor"
|
"Cursor"
|
||||||
"IntelliJ IDEA"
|
"IntelliJ IDEA"
|
||||||
"PyCharm"
|
"PyCharm"
|
||||||
"WebStorm"
|
"WebStorm"
|
||||||
"CLion"
|
"CLion"
|
||||||
"Rider"
|
"Rider"
|
||||||
"Sublime Text"
|
"Sublime Text"
|
||||||
"Blender"
|
"Blender"
|
||||||
"Godot"
|
"Godot"
|
||||||
"Unity"
|
"Unity"
|
||||||
"Unreal Editor"
|
"Unreal Editor"
|
||||||
)
|
)
|
||||||
|
|
||||||
FOCUS_APPS_PROCESSES=(
|
FOCUS_APPS_PROCESSES=(
|
||||||
"steam_app_"
|
"steam_app_"
|
||||||
"gamescope"
|
"gamescope"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if any focus app is running (window-based detection)
|
# Check if any focus app is running (window-based detection)
|
||||||
# Returns 0 if focus app found, 1 otherwise
|
# Returns 0 if focus app found, 1 otherwise
|
||||||
# Echoes the name of the found app
|
# Echoes the name of the found app
|
||||||
is_focus_app_running() {
|
is_focus_app_running() {
|
||||||
# Check windows first
|
# Check windows first
|
||||||
if command -v xdotool &>/dev/null; then
|
if command -v xdotool &> /dev/null; then
|
||||||
local app
|
local app
|
||||||
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
|
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"
|
echo "$app"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check specific processes
|
# Check specific processes
|
||||||
local app
|
local app
|
||||||
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
|
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
|
||||||
if pgrep -f "$app" &>/dev/null; then
|
if pgrep -f "$app" &> /dev/null; then
|
||||||
echo "$app"
|
echo "$app"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -210,69 +210,69 @@ is_focus_app_running() {
|
|||||||
# Check if a command exists
|
# Check if a command exists
|
||||||
# Usage: if require_command ffmpeg; then ...
|
# Usage: if require_command ffmpeg; then ...
|
||||||
require_command() {
|
require_command() {
|
||||||
local cmd="$1"
|
local cmd="$1"
|
||||||
local pkg="${2:-$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 "Error: '$cmd' is not installed or not in PATH." >&2
|
||||||
echo "Install with: sudo pacman -S $pkg" >&2
|
echo "Install with: sudo pacman -S $pkg" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for ImageMagick and display helpful installation message
|
# Check for ImageMagick and display helpful installation message
|
||||||
# Usage: require_imagemagick [optional: "magick" or "convert"]
|
# Usage: require_imagemagick [optional: "magick" or "convert"]
|
||||||
# Returns: Sets MAGICK_CMD variable to available command
|
# Returns: Sets MAGICK_CMD variable to available command
|
||||||
require_imagemagick() {
|
require_imagemagick() {
|
||||||
local preferred="${1:-}"
|
local preferred="${1:-}"
|
||||||
|
|
||||||
if [[ $preferred == "magick" ]] || [[ -z $preferred ]]; then
|
if [[ $preferred == "magick" ]] || [[ -z $preferred ]]; then
|
||||||
if command -v magick &>/dev/null; then
|
if command -v magick &> /dev/null; then
|
||||||
MAGICK_CMD="magick"
|
MAGICK_CMD="magick"
|
||||||
export MAGICK_CMD
|
export MAGICK_CMD
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $preferred == "convert" ]] || [[ -z $preferred ]]; then
|
if [[ $preferred == "convert" ]] || [[ -z $preferred ]]; then
|
||||||
if command -v convert &>/dev/null; then
|
if command -v convert &> /dev/null; then
|
||||||
MAGICK_CMD="convert"
|
MAGICK_CMD="convert"
|
||||||
export MAGICK_CMD
|
export MAGICK_CMD
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Error: ImageMagick is not installed." >&2
|
echo "Error: ImageMagick is not installed." >&2
|
||||||
echo "Install it with:" >&2
|
echo "Install it with:" >&2
|
||||||
echo " Arch Linux: sudo pacman -S imagemagick" >&2
|
echo " Arch Linux: sudo pacman -S imagemagick" >&2
|
||||||
echo " Ubuntu/Debian: sudo apt install imagemagick" >&2
|
echo " Ubuntu/Debian: sudo apt install imagemagick" >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install missing pacman packages
|
# Install missing pacman packages
|
||||||
# Usage: install_missing_pacman_packages pkg1 pkg2 pkg3 ...
|
# Usage: install_missing_pacman_packages pkg1 pkg2 pkg3 ...
|
||||||
# Returns 0 if all packages installed successfully, 1 otherwise
|
# Returns 0 if all packages installed successfully, 1 otherwise
|
||||||
install_missing_pacman_packages() {
|
install_missing_pacman_packages() {
|
||||||
local packages=("$@")
|
local packages=("$@")
|
||||||
local missing=()
|
local missing=()
|
||||||
|
|
||||||
for pkg in "${packages[@]}"; do
|
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")
|
missing+=("$pkg")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||||
echo "[INFO] All required packages are already installed."
|
echo "[INFO] All required packages are already installed."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[INFO] Installing missing packages: ${missing[*]}"
|
echo "[INFO] Installing missing packages: ${missing[*]}"
|
||||||
if ! sudo pacman -S --needed --noconfirm "${missing[@]}"; then
|
if ! sudo pacman -S --needed --noconfirm "${missing[@]}"; then
|
||||||
echo "[ERROR] Failed to install packages" >&2
|
echo "[ERROR] Failed to install packages" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -282,14 +282,14 @@ install_missing_pacman_packages() {
|
|||||||
# Send desktop notification (fails silently if notify-send not available)
|
# Send desktop notification (fails silently if notify-send not available)
|
||||||
# Usage: notify "Title" "Message" [urgency: low/normal/critical] [timeout_ms]
|
# Usage: notify "Title" "Message" [urgency: low/normal/critical] [timeout_ms]
|
||||||
notify() {
|
notify() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
local message="$2"
|
local message="$2"
|
||||||
local urgency="${3:-normal}"
|
local urgency="${3:-normal}"
|
||||||
local timeout="${4:-5000}"
|
local timeout="${4:-5000}"
|
||||||
|
|
||||||
if command -v notify-send &>/dev/null; then
|
if command -v notify-send &> /dev/null; then
|
||||||
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true
|
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -299,16 +299,16 @@ notify() {
|
|||||||
# Get the directory containing the calling script
|
# Get the directory containing the calling script
|
||||||
# Usage: SCRIPT_DIR=$(get_script_dir)
|
# Usage: SCRIPT_DIR=$(get_script_dir)
|
||||||
get_script_dir() {
|
get_script_dir() {
|
||||||
dirname "$(readlink -f "${BASH_SOURCE[1]:-$0}")"
|
dirname "$(readlink -f "${BASH_SOURCE[1]:-$0}")"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure a directory exists
|
# Ensure a directory exists
|
||||||
# Usage: ensure_dir "/path/to/dir"
|
# Usage: ensure_dir "/path/to/dir"
|
||||||
ensure_dir() {
|
ensure_dir() {
|
||||||
local dir="$1"
|
local dir="$1"
|
||||||
if [[ ! -d $dir ]]; then
|
if [[ ! -d $dir ]]; then
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -317,34 +317,34 @@ ensure_dir() {
|
|||||||
|
|
||||||
# Internal helper for running systemctl with optional --user flag
|
# Internal helper for running systemctl with optional --user flag
|
||||||
_systemctl_cmd() {
|
_systemctl_cmd() {
|
||||||
local user_flag="$1"
|
local user_flag="$1"
|
||||||
shift
|
shift
|
||||||
if [[ $user_flag == "--user" ]]; then
|
if [[ $user_flag == "--user" ]]; then
|
||||||
systemctl --user "$@"
|
systemctl --user "$@"
|
||||||
else
|
else
|
||||||
systemctl "$@"
|
systemctl "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enable and start a systemd service (user or system)
|
# Enable and start a systemd service (user or system)
|
||||||
# Usage: enable_service "service-name" [--user]
|
# Usage: enable_service "service-name" [--user]
|
||||||
enable_service() {
|
enable_service() {
|
||||||
local service="$1"
|
local service="$1"
|
||||||
local user_flag="${2:-}"
|
local user_flag="${2:-}"
|
||||||
_systemctl_cmd "$user_flag" daemon-reload
|
_systemctl_cmd "$user_flag" daemon-reload
|
||||||
_systemctl_cmd "$user_flag" enable --now "$service"
|
_systemctl_cmd "$user_flag" enable --now "$service"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a systemd service is active
|
# Check if a systemd service is active
|
||||||
# Usage: if is_service_active "service-name" [--user]; then ...
|
# Usage: if is_service_active "service-name" [--user]; then ...
|
||||||
is_service_active() {
|
is_service_active() {
|
||||||
_systemctl_cmd "${2:-}" is-active --quiet "$1"
|
_systemctl_cmd "${2:-}" is-active --quiet "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a systemd service is enabled
|
# Check if a systemd service is enabled
|
||||||
# Usage: if is_service_enabled "service-name" [--user]; then ...
|
# Usage: if is_service_enabled "service-name" [--user]; then ...
|
||||||
is_service_enabled() {
|
is_service_enabled() {
|
||||||
_systemctl_cmd "${2:-}" is-enabled --quiet "$1" 2>/dev/null
|
_systemctl_cmd "${2:-}" is-enabled --quiet "$1" 2> /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -359,19 +359,19 @@ declare -g COLOR_BLUE='\033[1;34m'
|
|||||||
declare -g COLOR_NC='\033[0m'
|
declare -g COLOR_NC='\033[0m'
|
||||||
|
|
||||||
log_info() {
|
log_info() {
|
||||||
printf "${COLOR_BLUE}[INFO]${COLOR_NC} %s\n" "$*"
|
printf "${COLOR_BLUE}[INFO]${COLOR_NC} %s\n" "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
log_ok() {
|
log_ok() {
|
||||||
printf "${COLOR_GREEN}[ OK ]${COLOR_NC} %s\n" "$*"
|
printf "${COLOR_GREEN}[ OK ]${COLOR_NC} %s\n" "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
log_warn() {
|
log_warn() {
|
||||||
printf "${COLOR_YELLOW}[WARN]${COLOR_NC} %s\n" "$*" >&2
|
printf "${COLOR_YELLOW}[WARN]${COLOR_NC} %s\n" "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
log_error() {
|
log_error() {
|
||||||
printf "${COLOR_RED}[ERROR]${COLOR_NC} %s\n" "$*" >&2
|
printf "${COLOR_RED}[ERROR]${COLOR_NC} %s\n" "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Alias for compatibility
|
# Alias for compatibility
|
||||||
@ -385,19 +385,19 @@ err() { log_error "$@"; }
|
|||||||
# Ask yes/no question, returns 0 for yes, 1 for no
|
# Ask yes/no question, returns 0 for yes, 1 for no
|
||||||
# Usage: if ask_yes_no "Continue?"; then ...
|
# Usage: if ask_yes_no "Continue?"; then ...
|
||||||
ask_yes_no() {
|
ask_yes_no() {
|
||||||
local prompt="$1"
|
local prompt="$1"
|
||||||
local ans
|
local ans
|
||||||
read -r -p "$prompt [y/N]: " ans || true
|
read -r -p "$prompt [y/N]: " ans || true
|
||||||
case "${ans:-}" in
|
case "${ans:-}" in
|
||||||
y | Y | yes | YES) return 0 ;;
|
y | Y | yes | YES) return 0 ;;
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a command is available
|
# Check if a command is available
|
||||||
# Usage: if has_cmd git; then ...
|
# Usage: if has_cmd git; then ...
|
||||||
has_cmd() {
|
has_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -407,18 +407,18 @@ has_cmd() {
|
|||||||
# Print a standard setup header for scripts
|
# Print a standard setup header for scripts
|
||||||
# Usage: print_setup_header "Script Name"
|
# Usage: print_setup_header "Script Name"
|
||||||
print_setup_header() {
|
print_setup_header() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
echo "$title"
|
echo "$title"
|
||||||
printf '=%.0s' $(seq 1 ${#title})
|
printf '=%.0s' $(seq 1 ${#title})
|
||||||
echo ""
|
echo ""
|
||||||
echo "Current Date: $(date)"
|
echo "Current Date: $(date)"
|
||||||
echo "User: $USER"
|
echo "User: $USER"
|
||||||
echo "Original user: $(get_actual_user)"
|
echo "Original user: $(get_actual_user)"
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
echo "Mode: Interactive (prompts enabled)"
|
echo "Mode: Interactive (prompts enabled)"
|
||||||
else
|
else
|
||||||
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -428,33 +428,33 @@ print_setup_header() {
|
|||||||
# Count mount layers for a path
|
# Count mount layers for a path
|
||||||
# Usage: count=$(mount_layers_count "/etc/hosts")
|
# Usage: count=$(mount_layers_count "/etc/hosts")
|
||||||
mount_layers_count() {
|
mount_layers_count() {
|
||||||
local target="$1"
|
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
|
# Collapse all bind mount layers for a path
|
||||||
# Usage: collapse_mounts "/etc/hosts" [max_iterations]
|
# Usage: collapse_mounts "/etc/hosts" [max_iterations]
|
||||||
collapse_mounts() {
|
collapse_mounts() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
local max_iter="${2:-20}"
|
local max_iter="${2:-20}"
|
||||||
local i=0
|
local i=0
|
||||||
|
|
||||||
if has_cmd mountpoint; then
|
if has_cmd mountpoint; then
|
||||||
while mountpoint -q "$target"; do
|
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=$((i + 1))
|
||||||
((i >= max_iter)) && break
|
((i >= max_iter)) && break
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
local cnt
|
local cnt
|
||||||
cnt=$(mount_layers_count "$target")
|
cnt=$(mount_layers_count "$target")
|
||||||
while ((cnt > 1)); do
|
while ((cnt > 1)); do
|
||||||
umount -l "$target" >/dev/null 2>&1 || break
|
umount -l "$target" > /dev/null 2>&1 || break
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
((i >= max_iter)) && break
|
((i >= max_iter)) && break
|
||||||
cnt=$(mount_layers_count "$target")
|
cnt=$(mount_layers_count "$target")
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -464,27 +464,27 @@ collapse_mounts() {
|
|||||||
# Validate resolution format (WIDTHxHEIGHT)
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
# Usage: if validate_resolution "1920x1080"; then ...
|
# Usage: if validate_resolution "1920x1080"; then ...
|
||||||
validate_resolution() {
|
validate_resolution() {
|
||||||
local res="$1"
|
local res="$1"
|
||||||
[[ $res =~ ^[0-9]+x[0-9]+$ ]]
|
[[ $res =~ ^[0-9]+x[0-9]+$ ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generate output filename with suffix
|
# Generate output filename with suffix
|
||||||
# Usage: output=$(generate_output_filename "input.jpg" "_resized")
|
# Usage: output=$(generate_output_filename "input.jpg" "_resized")
|
||||||
generate_output_filename() {
|
generate_output_filename() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
local suffix="$2"
|
local suffix="$2"
|
||||||
local ext="${3:-}"
|
local ext="${3:-}"
|
||||||
|
|
||||||
local basename dirname filename extension
|
local basename dirname filename extension
|
||||||
basename=$(basename "$input")
|
basename=$(basename "$input")
|
||||||
dirname=$(dirname "$input")
|
dirname=$(dirname "$input")
|
||||||
filename="${basename%.*}"
|
filename="${basename%.*}"
|
||||||
extension="${basename##*.}"
|
extension="${basename##*.}"
|
||||||
|
|
||||||
# Handle files without extension
|
# Handle files without extension
|
||||||
if [[ $filename == "$extension" ]]; then
|
if [[ $filename == "$extension" ]]; then
|
||||||
extension="${ext:-jpg}"
|
extension="${ext:-jpg}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "${dirname}/${filename}${suffix}.${extension}"
|
echo "${dirname}/${filename}${suffix}.${extension}"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ LIST_ONLY="false"
|
|||||||
VERBOSE="false"
|
VERBOSE="false"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage: $(basename "$0") [options]
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -50,120 +50,120 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--path)
|
--path)
|
||||||
ROOT_DIR="$2"
|
ROOT_DIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--skip-install)
|
--skip-install)
|
||||||
SKIP_INSTALL="true"
|
SKIP_INSTALL="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--install-only)
|
--install-only)
|
||||||
INSTALL_ONLY="true"
|
INSTALL_ONLY="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--list-only)
|
--list-only)
|
||||||
LIST_ONLY="true"
|
LIST_ONLY="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--verbose)
|
--verbose)
|
||||||
VERBOSE="true"
|
VERBOSE="true"
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown argument: $1"
|
log_error "Unknown argument: $1"
|
||||||
usage
|
usage
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ! -d $ROOT_DIR ]]; then
|
if [[ ! -d $ROOT_DIR ]]; then
|
||||||
log_error "Path not found: $ROOT_DIR"
|
log_error "Path not found: $ROOT_DIR"
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
is_cmd() { command -v "$1" >/dev/null 2>&1; }
|
is_cmd() { command -v "$1" > /dev/null 2>&1; }
|
||||||
|
|
||||||
is_arch() { is_cmd pacman; }
|
is_arch() { is_cmd pacman; }
|
||||||
have_aur_helper() { is_cmd yay || is_cmd paru; }
|
have_aur_helper() { is_cmd yay || is_cmd paru; }
|
||||||
|
|
||||||
install_if_missing() {
|
install_if_missing() {
|
||||||
local pkg cmd
|
local pkg cmd
|
||||||
pkg="$1"
|
pkg="$1"
|
||||||
cmd="$2"
|
cmd="$2"
|
||||||
if is_cmd "$cmd"; then
|
if is_cmd "$cmd"; then
|
||||||
[[ $VERBOSE == "true" ]] && log_info "Found $cmd"
|
[[ $VERBOSE == "true" ]] && log_info "Found $cmd"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $SKIP_INSTALL == "true" ]]; then
|
if [[ $SKIP_INSTALL == "true" ]]; then
|
||||||
log_warn "Skipping install of $pkg ($cmd not found)"
|
log_warn "Skipping install of $pkg ($cmd not found)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if is_arch; then
|
if is_arch; then
|
||||||
log_info "Installing $pkg via pacman..."
|
log_info "Installing $pkg via pacman..."
|
||||||
if ! sudo pacman -S --needed --noconfirm "$pkg"; then
|
if ! sudo pacman -S --needed --noconfirm "$pkg"; then
|
||||||
log_warn "Failed to install $pkg via pacman."
|
log_warn "Failed to install $pkg via pacman."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
log_warn "Non-Arch system detected. Please install '$pkg' manually."
|
log_warn "Non-Arch system detected. Please install '$pkg' manually."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_linters() {
|
install_linters() {
|
||||||
local ok=0
|
local ok=0
|
||||||
|
|
||||||
# Core linters
|
# Core linters
|
||||||
install_if_missing shellcheck shellcheck || ok=1
|
install_if_missing shellcheck shellcheck || ok=1
|
||||||
install_if_missing shfmt shfmt || ok=1
|
install_if_missing shfmt shfmt || ok=1
|
||||||
|
|
||||||
# Optional linters (best-effort)
|
# Optional linters (best-effort)
|
||||||
# checkbashisms may be in repos or AUR; try pacman first, then AUR helper
|
# checkbashisms may be in repos or AUR; try pacman first, then AUR helper
|
||||||
if ! is_cmd checkbashisms; then
|
if ! is_cmd checkbashisms; then
|
||||||
if is_arch; 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
|
if have_aur_helper; then
|
||||||
log_info "Installing checkbashisms from AUR (requires yay/paru)..."
|
log_info "Installing checkbashisms from AUR (requires yay/paru)..."
|
||||||
if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi
|
if is_cmd yay; then yay -S --noconfirm checkbashisms || true; fi
|
||||||
if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi
|
if is_cmd paru; then paru -S --noconfirm checkbashisms || true; fi
|
||||||
else
|
else
|
||||||
log_warn "checkbashisms not installed (no AUR helper)."
|
log_warn "checkbashisms not installed (no AUR helper)."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# bashate (python-based), typically available as python-bashate in AUR
|
# bashate (python-based), typically available as python-bashate in AUR
|
||||||
if ! is_cmd bashate; then
|
if ! is_cmd bashate; then
|
||||||
if is_arch && have_aur_helper; then
|
if is_arch && have_aur_helper; then
|
||||||
log_info "Installing bashate from AUR (requires yay/paru)..."
|
log_info "Installing bashate from AUR (requires yay/paru)..."
|
||||||
if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi
|
if is_cmd yay; then yay -S --noconfirm python-bashate || true; fi
|
||||||
if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi
|
if is_cmd paru; then paru -S --noconfirm python-bashate || true; fi
|
||||||
else
|
else
|
||||||
# Try pip if user has it and wants to
|
# Try pip if user has it and wants to
|
||||||
if is_cmd pipx; then
|
if is_cmd pipx; then
|
||||||
log_info "Installing bashate via pipx..."
|
log_info "Installing bashate via pipx..."
|
||||||
pipx install bashate || true
|
pipx install bashate || true
|
||||||
elif is_cmd pip3; then
|
elif is_cmd pip3; then
|
||||||
log_info "Installing bashate via pip (user)..."
|
log_info "Installing bashate via pip (user)..."
|
||||||
pip3 install --user bashate || true
|
pip3 install --user bashate || true
|
||||||
else
|
else
|
||||||
log_warn "bashate not installed (no AUR helper or pip available)."
|
log_warn "bashate not installed (no AUR helper or pip available)."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return "$ok"
|
return "$ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
TMPDIR=$(mktemp -d)
|
TMPDIR=$(mktemp -d)
|
||||||
@ -173,255 +173,255 @@ ABS_FILES_Z="$TMPDIR/files_abs.zlist"
|
|||||||
REL_FILES_Z="$TMPDIR/files_rel.zlist"
|
REL_FILES_Z="$TMPDIR/files_rel.zlist"
|
||||||
|
|
||||||
discover_shell_files() {
|
discover_shell_files() {
|
||||||
local base="$1"
|
local base="$1"
|
||||||
local -a all
|
local -a all
|
||||||
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 -z)
|
||||||
while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z)
|
while IFS= read -r -d '' f; do all+=("$f"); done < <(git -C "$base" ls-files --others --exclude-standard -z)
|
||||||
else
|
else
|
||||||
while IFS= read -r -d '' f; do
|
while IFS= read -r -d '' f; do
|
||||||
# trim leading ./ to keep consistent style with git paths
|
# trim leading ./ to keep consistent style with git paths
|
||||||
f="${f#./}"
|
f="${f#./}"
|
||||||
f="${f#"${base}"/}"
|
f="${f#"${base}"/}"
|
||||||
all+=("$f")
|
all+=("$f")
|
||||||
done < <(find "$base" -type f -print0)
|
done < <(find "$base" -type f -print0)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local -a shells
|
local -a shells
|
||||||
shells=()
|
shells=()
|
||||||
|
|
||||||
for rel in "${all[@]}"; do
|
for rel in "${all[@]}"; do
|
||||||
# skip binary-ish or huge files quickly by extension heuristic
|
# skip binary-ish or huge files quickly by extension heuristic
|
||||||
case "$rel" in
|
case "$rel" in
|
||||||
*.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin)
|
*.png | *.jpg | *.jpeg | *.gif | *.ico | *.pdf | *.svg | *.zip | *.tar | *.gz | *.xz | *.7z | *.so | *.o | *.bin)
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
local abs="$base/$rel"
|
local abs="$base/$rel"
|
||||||
[[ -f $abs && -r $abs ]] || continue
|
[[ -f $abs && -r $abs ]] || continue
|
||||||
|
|
||||||
if [[ $rel == *.sh || $rel == *.bash || $rel == *.zsh ]]; then
|
if [[ $rel == *.sh || $rel == *.bash || $rel == *.zsh ]]; then
|
||||||
shells+=("$rel")
|
shells+=("$rel")
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check shebang
|
# Check shebang
|
||||||
local first
|
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
|
if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
|
||||||
shells+=("$rel")
|
shells+=("$rel")
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Also catch executable files with shell shebang even without extension
|
# Also catch executable files with shell shebang even without extension
|
||||||
if [[ -x $abs ]]; then
|
if [[ -x $abs ]]; then
|
||||||
if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
|
if [[ $first =~ ^#! && $first =~ (ba|z|d|k)?sh ]]; then
|
||||||
shells+=("$rel")
|
shells+=("$rel")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# write lists
|
# write lists
|
||||||
: >"$REL_FILES_Z"
|
: > "$REL_FILES_Z"
|
||||||
: >"$ABS_FILES_Z"
|
: > "$ABS_FILES_Z"
|
||||||
for rel in "${shells[@]}"; do
|
for rel in "${shells[@]}"; do
|
||||||
printf '%s\0' "$rel" >>"$REL_FILES_Z"
|
printf '%s\0' "$rel" >> "$REL_FILES_Z"
|
||||||
printf '%s\0' "$base/$rel" >>"$ABS_FILES_Z"
|
printf '%s\0' "$base/$rel" >> "$ABS_FILES_Z"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
print_file_list() {
|
print_file_list() {
|
||||||
local count
|
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"
|
log_info "Discovered $count shell file(s) under $ROOT_DIR"
|
||||||
if [[ $VERBOSE == "true" ]]; then
|
if [[ $VERBOSE == "true" ]]; then
|
||||||
tr '\0' '\n' <"$REL_FILES_Z" | sed 's/^/ - /'
|
tr '\0' '\n' < "$REL_FILES_Z" | sed 's/^/ - /'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
run_linters() {
|
run_linters() {
|
||||||
local issues=0
|
local issues=0
|
||||||
local count
|
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
|
if [[ $count -eq 0 ]]; then
|
||||||
log_warn "No shell files found to lint."
|
log_warn "No shell files found to lint."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mapfile -d '' -t FILES <"$ABS_FILES_Z"
|
mapfile -d '' -t FILES < "$ABS_FILES_Z"
|
||||||
|
|
||||||
log_info "Running shellcheck..."
|
log_info "Running shellcheck..."
|
||||||
local sc_out="$TMPDIR/shellcheck.txt"
|
local sc_out="$TMPDIR/shellcheck.txt"
|
||||||
if is_cmd shellcheck; then
|
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))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_warn "shellcheck not found; skipping"
|
log_warn "shellcheck not found; skipping"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Running shfmt (diff mode)..."
|
log_info "Running shfmt (diff mode)..."
|
||||||
local shfmt_out="$TMPDIR/shfmt.diff"
|
local shfmt_out="$TMPDIR/shfmt.diff"
|
||||||
if is_cmd shfmt; then
|
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
|
# shfmt returns non-zero when diff exists
|
||||||
issues=$((issues + 1))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_warn "shfmt not found; skipping"
|
log_warn "shfmt not found; skipping"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Running checkbashisms (optional)..."
|
log_info "Running checkbashisms (optional)..."
|
||||||
local cbi_out="$TMPDIR/checkbashisms.txt"
|
local cbi_out="$TMPDIR/checkbashisms.txt"
|
||||||
local cbi_status=0
|
local cbi_status=0
|
||||||
if is_cmd checkbashisms; then
|
if is_cmd checkbashisms; then
|
||||||
# Only run checkbashisms on scripts that are intended for /bin/sh (or unspecified),
|
# Only run checkbashisms on scripts that are intended for /bin/sh (or unspecified),
|
||||||
# skip explicit bash/zsh scripts to avoid false positives.
|
# skip explicit bash/zsh scripts to avoid false positives.
|
||||||
local -a CBI_FILES
|
local -a CBI_FILES
|
||||||
CBI_FILES=()
|
CBI_FILES=()
|
||||||
for f in "${FILES[@]}"; do
|
for f in "${FILES[@]}"; do
|
||||||
local first
|
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
|
if [[ $first =~ bash || $first =~ zsh ]]; then
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
CBI_FILES+=("$f")
|
CBI_FILES+=("$f")
|
||||||
done
|
done
|
||||||
if [[ ${#CBI_FILES[@]} -gt 0 ]]; then
|
if [[ ${#CBI_FILES[@]} -gt 0 ]]; then
|
||||||
# checkbashisms exits 0 if OK, 1 if issues, other codes for tool warnings
|
# 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
|
else
|
||||||
: >"$cbi_out"
|
: > "$cbi_out"
|
||||||
fi
|
fi
|
||||||
cbi_status=$?
|
cbi_status=$?
|
||||||
if [[ $cbi_status -eq 1 ]]; then
|
if [[ $cbi_status -eq 1 ]]; then
|
||||||
issues=$((issues + 1))
|
issues=$((issues + 1))
|
||||||
elif [[ $cbi_status -ne 0 ]]; then
|
elif [[ $cbi_status -ne 0 ]]; then
|
||||||
log_warn "checkbashisms exited with status $cbi_status (treated as warning)"
|
log_warn "checkbashisms exited with status $cbi_status (treated as warning)"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_warn "checkbashisms not found; skipping"
|
log_warn "checkbashisms not found; skipping"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Running bash/zsh/sh syntax checks (-n)..."
|
log_info "Running bash/zsh/sh syntax checks (-n)..."
|
||||||
local bash_out="$TMPDIR/bash_syntax.txt"
|
local bash_out="$TMPDIR/bash_syntax.txt"
|
||||||
local zsh_out="$TMPDIR/zsh_syntax.txt"
|
local zsh_out="$TMPDIR/zsh_syntax.txt"
|
||||||
local sh_out="$TMPDIR/sh_syntax.txt"
|
local sh_out="$TMPDIR/sh_syntax.txt"
|
||||||
|
|
||||||
# Partition files by shebang for better accuracy
|
# Partition files by shebang for better accuracy
|
||||||
local -a BASH_FILES ZSH_FILES SH_FILES
|
local -a BASH_FILES ZSH_FILES SH_FILES
|
||||||
BASH_FILES=()
|
BASH_FILES=()
|
||||||
ZSH_FILES=()
|
ZSH_FILES=()
|
||||||
SH_FILES=()
|
SH_FILES=()
|
||||||
for f in "${FILES[@]}"; do
|
for f in "${FILES[@]}"; do
|
||||||
local first
|
local first
|
||||||
first=$(head -n 1 -- "$f" 2>/dev/null || true)
|
first=$(head -n 1 -- "$f" 2> /dev/null || true)
|
||||||
if [[ $first =~ bash ]]; then
|
if [[ $first =~ bash ]]; then
|
||||||
BASH_FILES+=("$f")
|
BASH_FILES+=("$f")
|
||||||
elif [[ $first =~ zsh ]]; then
|
elif [[ $first =~ zsh ]]; then
|
||||||
ZSH_FILES+=("$f")
|
ZSH_FILES+=("$f")
|
||||||
else
|
else
|
||||||
SH_FILES+=("$f")
|
SH_FILES+=("$f")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#BASH_FILES[@]} -gt 0 ]] && is_cmd bash; then
|
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))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ ${#ZSH_FILES[@]} -gt 0 ]] && is_cmd zsh; then
|
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))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# prefer dash if present for /bin/sh style
|
# prefer dash if present for /bin/sh style
|
||||||
if [[ ${#SH_FILES[@]} -gt 0 ]]; then
|
if [[ ${#SH_FILES[@]} -gt 0 ]]; then
|
||||||
if is_cmd dash; 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))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
elif is_cmd sh; then
|
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))
|
issues=$((issues + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
log_info "========== Shell Lint Report =========="
|
log_info "========== Shell Lint Report =========="
|
||||||
|
|
||||||
if [[ -s $sc_out ]]; then
|
if [[ -s $sc_out ]]; then
|
||||||
printf '\n\033[1m-- shellcheck --\033[0m\n'
|
printf '\n\033[1m-- shellcheck --\033[0m\n'
|
||||||
cat "$sc_out"
|
cat "$sc_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n'
|
printf '\n\033[1;32m-- shellcheck: PASS (no issues) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $shfmt_out ]]; then
|
if [[ -s $shfmt_out ]]; then
|
||||||
printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n'
|
printf '\n\033[1m-- shfmt (diffs found) --\033[0m\n'
|
||||||
cat "$shfmt_out"
|
cat "$shfmt_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n'
|
printf '\n\033[1;32m-- shfmt: PASS (formatted) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $cbi_out ]]; then
|
if [[ -s $cbi_out ]]; then
|
||||||
printf '\n\033[1m-- checkbashisms --\033[0m\n'
|
printf '\n\033[1m-- checkbashisms --\033[0m\n'
|
||||||
cat "$cbi_out"
|
cat "$cbi_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n'
|
printf '\n\033[1;32m-- checkbashisms: PASS (or skipped) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $bash_out ]]; then
|
if [[ -s $bash_out ]]; then
|
||||||
printf '\n\033[1m-- bash -n (syntax) --\033[0m\n'
|
printf '\n\033[1m-- bash -n (syntax) --\033[0m\n'
|
||||||
cat "$bash_out"
|
cat "$bash_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n'
|
printf '\n\033[1;32m-- bash -n: PASS (or none) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $zsh_out ]]; then
|
if [[ -s $zsh_out ]]; then
|
||||||
printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n'
|
printf '\n\033[1m-- zsh -n (syntax) --\033[0m\n'
|
||||||
cat "$zsh_out"
|
cat "$zsh_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n'
|
printf '\n\033[1;32m-- zsh -n: PASS (or none) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -s $sh_out ]]; then
|
if [[ -s $sh_out ]]; then
|
||||||
printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n'
|
printf '\n\033[1m-- sh/dash -n (syntax) --\033[0m\n'
|
||||||
cat "$sh_out"
|
cat "$sh_out"
|
||||||
else
|
else
|
||||||
printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n'
|
printf '\n\033[1;32m-- sh/dash -n: PASS (or none) --\033[0m\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
if [[ $issues -gt 0 ]]; then
|
if [[ $issues -gt 0 ]]; then
|
||||||
log_error "Linting completed with $issues tool(s) reporting issues."
|
log_error "Linting completed with $issues tool(s) reporting issues."
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
log_info "All checks passed."
|
log_info "All checks passed."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
if [[ $INSTALL_ONLY == "true" ]]; then
|
if [[ $INSTALL_ONLY == "true" ]]; then
|
||||||
install_linters
|
install_linters
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Only attempt installs if not list-only
|
# Only attempt installs if not list-only
|
||||||
if [[ $LIST_ONLY != "true" ]]; then
|
if [[ $LIST_ONLY != "true" ]]; then
|
||||||
install_linters || true
|
install_linters || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
discover_shell_files "$ROOT_DIR"
|
discover_shell_files "$ROOT_DIR"
|
||||||
print_file_list
|
print_file_list
|
||||||
|
|
||||||
if [[ $LIST_ONLY == "true" ]]; then
|
if [[ $LIST_ONLY == "true" ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_linters
|
run_linters
|
||||||
|
|||||||
@ -28,7 +28,7 @@ SET_DEFAULT=false
|
|||||||
DO_RESTART=false
|
DO_RESTART=false
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
fix_thorium_unity.sh - Auto-allow unityhub:// from Unity origins in Thorium/Chromium
|
fix_thorium_unity.sh - Auto-allow unityhub:// from Unity origins in Thorium/Chromium
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -44,52 +44,52 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--policy)
|
--policy)
|
||||||
DO_POLICY=true
|
DO_POLICY=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--set-default)
|
--set-default)
|
||||||
SET_DEFAULT=true
|
SET_DEFAULT=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--restart)
|
--restart)
|
||||||
DO_RESTART=true
|
DO_RESTART=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown argument: $1"
|
log_error "Unknown argument: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
ensure_sudo() {
|
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."
|
log_error "sudo not found; cannot install system policy. Use --set-default or run from root."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_policy() {
|
install_policy() {
|
||||||
ensure_sudo
|
ensure_sudo
|
||||||
# Candidate policy directories (most common for Chromium forks)
|
# Candidate policy directories (most common for Chromium forks)
|
||||||
local candidates=(
|
local candidates=(
|
||||||
"/etc/thorium-browser/policies/managed" # Thorium
|
"/etc/thorium-browser/policies/managed" # Thorium
|
||||||
"/etc/chromium/policies/managed" # Chromium
|
"/etc/chromium/policies/managed" # Chromium
|
||||||
"/etc/opt/chrome/policies/managed" # Google Chrome
|
"/etc/opt/chrome/policies/managed" # Google Chrome
|
||||||
)
|
)
|
||||||
local wrote_any=false
|
local wrote_any=false
|
||||||
for target in "${candidates[@]}"; do
|
for target in "${candidates[@]}"; do
|
||||||
log_info "Installing policy into: $target"
|
log_info "Installing policy into: $target"
|
||||||
sudo mkdir -p "$target"
|
sudo mkdir -p "$target"
|
||||||
local policy_file="$target/unityhub-policy.json"
|
local policy_file="$target/unityhub-policy.json"
|
||||||
sudo tee "$policy_file" >/dev/null <<'JSON'
|
sudo tee "$policy_file" > /dev/null << 'JSON'
|
||||||
{
|
{
|
||||||
"AutoLaunchProtocolsFromOrigins": [
|
"AutoLaunchProtocolsFromOrigins": [
|
||||||
{ "protocol": "unityhub", "origin": "https://id.unity.com", "allow": true },
|
{ "protocol": "unityhub", "origin": "https://id.unity.com", "allow": true },
|
||||||
@ -101,53 +101,53 @@ install_policy() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
# Some Chromium builds cache policies; no explicit reload on Linux. Restarting browser suffices.
|
# Some Chromium builds cache policies; no explicit reload on Linux. Restarting browser suffices.
|
||||||
log_ok "Policy written: $policy_file"
|
log_ok "Policy written: $policy_file"
|
||||||
wrote_any=true
|
wrote_any=true
|
||||||
done
|
done
|
||||||
if [[ $wrote_any != true ]]; then
|
if [[ $wrote_any != true ]]; then
|
||||||
log_warn "Policy may not have been written. No candidate directories processed."
|
log_warn "Policy may not have been written. No candidate directories processed."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
set_default_browser() {
|
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
|
# Prefer the upstream desktop id if it exists
|
||||||
local desktop="thorium-browser.desktop"
|
local desktop="thorium-browser.desktop"
|
||||||
if [[ ! -f "/usr/share/applications/$desktop" && -f "$HOME/.local/share/applications/$desktop" ]]; then
|
if [[ ! -f "/usr/share/applications/$desktop" && -f "$HOME/.local/share/applications/$desktop" ]]; then
|
||||||
: # keep desktop as is
|
: # keep desktop as is
|
||||||
elif [[ ! -f "/usr/share/applications/$desktop" && ! -f "$HOME/.local/share/applications/$desktop" ]]; then
|
elif [[ ! -f "/usr/share/applications/$desktop" && ! -f "$HOME/.local/share/applications/$desktop" ]]; then
|
||||||
log_warn "thorium-browser.desktop not found; leaving default browser unchanged."
|
log_warn "thorium-browser.desktop not found; leaving default browser unchanged."
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
log_info "Setting default browser to $desktop"
|
log_info "Setting default browser to $desktop"
|
||||||
xdg-settings set default-web-browser "$desktop" || log_warn "Failed to set default browser via xdg-settings"
|
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
|
else
|
||||||
log_warn "xdg-settings not found; cannot set default browser automatically."
|
log_warn "xdg-settings not found; cannot set default browser automatically."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
restart_thorium() {
|
restart_thorium() {
|
||||||
# Kill Thorium processes and start fresh
|
# Kill Thorium processes and start fresh
|
||||||
log_info "Restarting Thorium..."
|
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)
|
# 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
|
# Start Thorium detached if available
|
||||||
if command -v thorium-browser >/dev/null 2>&1; then
|
if command -v thorium-browser > /dev/null 2>&1; then
|
||||||
nohup thorium-browser >/dev/null 2>&1 &
|
nohup thorium-browser > /dev/null 2>&1 &
|
||||||
disown || true
|
disown || true
|
||||||
fi
|
fi
|
||||||
log_ok "Thorium restart attempted."
|
log_ok "Thorium restart attempted."
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
$DO_POLICY && install_policy
|
$DO_POLICY && install_policy
|
||||||
$SET_DEFAULT && set_default_browser
|
$SET_DEFAULT && set_default_browser
|
||||||
$DO_RESTART && restart_thorium
|
$DO_RESTART && restart_thorium
|
||||||
|
|
||||||
cat <<'NEXT'
|
cat << 'NEXT'
|
||||||
---
|
---
|
||||||
Next steps:
|
Next steps:
|
||||||
- Open Unity Hub, click Sign in, complete in Thorium; when prompted, allow the unityhub link to open the app.
|
- 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"
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
${SCRIPT_NAME} - Fix Unity Hub sign-in by registering unityhub:// URL handler
|
${SCRIPT_NAME} - Fix Unity Hub sign-in by registering unityhub:// URL handler
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -47,158 +47,158 @@ AUTO_INSTALL=false
|
|||||||
RUN_TEST=false
|
RUN_TEST=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-y | --yes)
|
-y | --yes)
|
||||||
AUTO_INSTALL=true
|
AUTO_INSTALL=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--test)
|
--test)
|
||||||
RUN_TEST=true
|
RUN_TEST=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log_error "Unknown argument: $1"
|
log_error "Unknown argument: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
require_cmd() {
|
require_cmd() {
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
if ! command -v "$1" > /dev/null 2>&1; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_deps_arch() {
|
ensure_deps_arch() {
|
||||||
# Best-effort install for Arch-based systems
|
# Best-effort install for Arch-based systems
|
||||||
if [[ $AUTO_INSTALL != true ]]; then
|
if [[ $AUTO_INSTALL != true ]]; then
|
||||||
log_warn "Skipping package installation (use -y to auto-install)."
|
log_warn "Skipping package installation (use -y to auto-install)."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if ! require_cmd pacman; then
|
if ! require_cmd pacman; then
|
||||||
log_warn "Not an Arch-based system (pacman not found). Skipping auto-install."
|
log_warn "Not an Arch-based system (pacman not found). Skipping auto-install."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
local pkgs=(xdg-utils desktop-file-utils xdg-desktop-portal xdg-desktop-portal-gtk)
|
local pkgs=(xdg-utils desktop-file-utils xdg-desktop-portal xdg-desktop-portal-gtk)
|
||||||
log_info "Installing/ensuring packages: ${pkgs[*]}"
|
log_info "Installing/ensuring packages: ${pkgs[*]}"
|
||||||
if ! require_cmd sudo; then
|
if ! require_cmd sudo; then
|
||||||
log_warn "sudo not found; attempting pacman directly (may fail)."
|
log_warn "sudo not found; attempting pacman directly (may fail)."
|
||||||
sudo_cmd=""
|
sudo_cmd=""
|
||||||
else
|
else
|
||||||
sudo_cmd="sudo"
|
sudo_cmd="sudo"
|
||||||
fi
|
fi
|
||||||
# Use --needed to avoid reinstalling
|
# Use --needed to avoid reinstalling
|
||||||
set +e
|
set +e
|
||||||
$sudo_cmd pacman -S --needed --noconfirm "${pkgs[@]}"
|
$sudo_cmd pacman -S --needed --noconfirm "${pkgs[@]}"
|
||||||
local rc=$?
|
local rc=$?
|
||||||
set -e
|
set -e
|
||||||
if [[ $rc -ne 0 ]]; then
|
if [[ $rc -ne 0 ]]; then
|
||||||
log_warn "Package install may have failed or been partial. Continuing anyway."
|
log_warn "Package install may have failed or been partial. Continuing anyway."
|
||||||
else
|
else
|
||||||
log_ok "Dependencies installed/verified."
|
log_ok "Dependencies installed/verified."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
desktop_dir="$HOME/.local/share/applications"
|
desktop_dir="$HOME/.local/share/applications"
|
||||||
mkdir -p "$desktop_dir"
|
mkdir -p "$desktop_dir"
|
||||||
|
|
||||||
detect_unityhub() {
|
detect_unityhub() {
|
||||||
# Outputs: INSTALL_TYPE (FLATPAK|NATIVE|APPIMAGE|UNKNOWN) and EXEC_CMD
|
# Outputs: INSTALL_TYPE (FLATPAK|NATIVE|APPIMAGE|UNKNOWN) and EXEC_CMD
|
||||||
local install_type="UNKNOWN" exec_cmd=""
|
local install_type="UNKNOWN" exec_cmd=""
|
||||||
|
|
||||||
# 1) Flatpak
|
# 1) Flatpak
|
||||||
if command -v flatpak >/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
|
if flatpak info com.unity.UnityHub > /dev/null 2>&1; then
|
||||||
install_type="FLATPAK"
|
install_type="FLATPAK"
|
||||||
exec_cmd="flatpak run com.unity.UnityHub %U"
|
exec_cmd="flatpak run com.unity.UnityHub %U"
|
||||||
echo "$install_type|$exec_cmd"
|
echo "$install_type|$exec_cmd"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2) Native binary in PATH
|
# 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
|
local path
|
||||||
path="$(command -v unityhub)"
|
path="$(command -v unityhub)"
|
||||||
install_type="NATIVE"
|
install_type="NATIVE"
|
||||||
exec_cmd="$path %U"
|
exec_cmd="$path %U"
|
||||||
echo "$install_type|$exec_cmd"
|
echo "$install_type|$exec_cmd"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3) Search desktop files for Unity Hub Exec
|
# 3) Search desktop files for Unity Hub Exec
|
||||||
local search_dirs=(
|
local search_dirs=(
|
||||||
"$HOME/.local/share/applications"
|
"$HOME/.local/share/applications"
|
||||||
"/usr/share/applications"
|
"/usr/share/applications"
|
||||||
"/var/lib/flatpak/exports/share/applications"
|
"/var/lib/flatpak/exports/share/applications"
|
||||||
"$HOME/.local/share/flatpak/exports/share/applications"
|
"$HOME/.local/share/flatpak/exports/share/applications"
|
||||||
)
|
)
|
||||||
local found_exec=""
|
local found_exec=""
|
||||||
for d in "${search_dirs[@]}"; do
|
for d in "${search_dirs[@]}"; do
|
||||||
[[ -d $d ]] || continue
|
[[ -d $d ]] || continue
|
||||||
# prefer official naming when present
|
# prefer official naming when present
|
||||||
local f
|
local f
|
||||||
for f in "$d"/*.desktop; do
|
for f in "$d"/*.desktop; do
|
||||||
[[ -e $f ]] || continue
|
[[ -e $f ]] || continue
|
||||||
if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2>/dev/null ||
|
if grep -qiE '^(Name|Comment)=.*Unity Hub' "$f" 2> /dev/null ||
|
||||||
grep -qiE 'Exec=.*unityhub' "$f" 2>/dev/null; then
|
grep -qiE 'Exec=.*unityhub' "$f" 2> /dev/null; then
|
||||||
local exec_line
|
local exec_line
|
||||||
exec_line="$(grep -iE '^Exec=' "$f" | head -n1 | sed 's/^Exec=//')"
|
exec_line="$(grep -iE '^Exec=' "$f" | head -n1 | sed 's/^Exec=//')"
|
||||||
if [[ -n $exec_line ]]; then
|
if [[ -n $exec_line ]]; then
|
||||||
found_exec="$exec_line"
|
found_exec="$exec_line"
|
||||||
break 2
|
break 2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n $found_exec ]]; then
|
if [[ -n $found_exec ]]; then
|
||||||
# Normalize: ensure %U present
|
# Normalize: ensure %U present
|
||||||
if [[ $found_exec != *"%U"* && $found_exec != *"%u"* ]]; then
|
if [[ $found_exec != *"%U"* && $found_exec != *"%u"* ]]; then
|
||||||
found_exec+=" %U"
|
found_exec+=" %U"
|
||||||
fi
|
fi
|
||||||
if [[ $found_exec == flatpak* ]]; then
|
if [[ $found_exec == flatpak* ]]; then
|
||||||
install_type="FLATPAK"
|
install_type="FLATPAK"
|
||||||
elif [[ $found_exec == *AppImage* || $found_exec == *appimage* ]]; then
|
elif [[ $found_exec == *AppImage* || $found_exec == *appimage* ]]; then
|
||||||
install_type="APPIMAGE"
|
install_type="APPIMAGE"
|
||||||
else
|
else
|
||||||
install_type="NATIVE"
|
install_type="NATIVE"
|
||||||
fi
|
fi
|
||||||
echo "$install_type|$found_exec"
|
echo "$install_type|$found_exec"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4) Try common AppImage locations
|
# 4) Try common AppImage locations
|
||||||
local ai_candidates=(
|
local ai_candidates=(
|
||||||
"$HOME/Applications/UnityHub*.AppImage"
|
"$HOME/Applications/UnityHub*.AppImage"
|
||||||
"$HOME/.local/bin/UnityHub*.AppImage"
|
"$HOME/.local/bin/UnityHub*.AppImage"
|
||||||
"/opt/UnityHub*/UnityHub*.AppImage"
|
"/opt/UnityHub*/UnityHub*.AppImage"
|
||||||
)
|
)
|
||||||
local ai
|
local ai
|
||||||
for ai in "${ai_candidates[@]}"; do
|
for ai in "${ai_candidates[@]}"; do
|
||||||
for p in $ai; do
|
for p in $ai; do
|
||||||
if [[ -f $p && -x $p ]]; then
|
if [[ -f $p && -x $p ]]; then
|
||||||
install_type="APPIMAGE"
|
install_type="APPIMAGE"
|
||||||
exec_cmd="$p %U"
|
exec_cmd="$p %U"
|
||||||
echo "$install_type|$exec_cmd"
|
echo "$install_type|$exec_cmd"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "$install_type|$exec_cmd"
|
echo "$install_type|$exec_cmd"
|
||||||
}
|
}
|
||||||
|
|
||||||
create_handler_desktop() {
|
create_handler_desktop() {
|
||||||
local exec_cmd="$1"
|
local exec_cmd="$1"
|
||||||
local dest="$desktop_dir/unityhub-url-handler.desktop"
|
local dest="$desktop_dir/unityhub-url-handler.desktop"
|
||||||
log_info "Writing handler desktop entry: $dest"
|
log_info "Writing handler desktop entry: $dest"
|
||||||
cat >"$dest" <<DESK
|
cat > "$dest" << DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=Unity Hub URL Handler
|
Name=Unity Hub URL Handler
|
||||||
Comment=Handle unityhub:// links for Unity Hub sign-in
|
Comment=Handle unityhub:// links for Unity Hub sign-in
|
||||||
@ -211,82 +211,82 @@ StartupWMClass=Unity Hub
|
|||||||
MimeType=x-scheme-handler/unityhub;x-scheme-handler/unity;
|
MimeType=x-scheme-handler/unityhub;x-scheme-handler/unity;
|
||||||
NoDisplay=true
|
NoDisplay=true
|
||||||
DESK
|
DESK
|
||||||
log_ok "Desktop entry created/updated."
|
log_ok "Desktop entry created/updated."
|
||||||
echo "$dest"
|
echo "$dest"
|
||||||
}
|
}
|
||||||
|
|
||||||
register_mime_handler() {
|
register_mime_handler() {
|
||||||
local desktop_file="$1"
|
local desktop_file="$1"
|
||||||
# Update desktop database if available
|
# 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
|
update-desktop-database "$desktop_dir" || true
|
||||||
else
|
else
|
||||||
log_warn "update-desktop-database not found (install desktop-file-utils)."
|
log_warn "update-desktop-database not found (install desktop-file-utils)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Register as default handler for both schemes
|
# 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/unityhub || true
|
||||||
xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unity || true
|
xdg-mime default "$(basename "$desktop_file")" x-scheme-handler/unity || true
|
||||||
else
|
else
|
||||||
log_error "xdg-mime not found (install xdg-utils)."
|
log_error "xdg-mime not found (install xdg-utils)."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
log_ok "MIME handler registered for unityhub:// (and unity://)."
|
log_ok "MIME handler registered for unityhub:// (and unity://)."
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_registration() {
|
verify_registration() {
|
||||||
local expected cur1 cur2
|
local expected cur1 cur2
|
||||||
expected="$(basename "$1")"
|
expected="$(basename "$1")"
|
||||||
cur1="$(xdg-mime query default x-scheme-handler/unityhub 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)"
|
cur2="$(xdg-mime query default x-scheme-handler/unity 2> /dev/null || true)"
|
||||||
log_info "Current handler (unityhub): ${cur1:-<none>}"
|
log_info "Current handler (unityhub): ${cur1:-<none>}"
|
||||||
log_info "Current handler (unity): ${cur2:-<none>}"
|
log_info "Current handler (unity): ${cur2:-<none>}"
|
||||||
if [[ $cur1 == "$expected" ]]; then
|
if [[ $cur1 == "$expected" ]]; then
|
||||||
log_ok "unityhub scheme correctly set to $expected"
|
log_ok "unityhub scheme correctly set to $expected"
|
||||||
else
|
else
|
||||||
log_warn "unityhub scheme not set to $expected (currently: ${cur1:-none})."
|
log_warn "unityhub scheme not set to $expected (currently: ${cur1:-none})."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_test_open() {
|
maybe_test_open() {
|
||||||
if [[ $RUN_TEST == true ]]; then
|
if [[ $RUN_TEST == true ]]; then
|
||||||
log_info "Opening test link: unityhub://v1/editor-signin"
|
log_info "Opening test link: unityhub://v1/editor-signin"
|
||||||
if command -v xdg-open >/dev/null 2>&1; then
|
if command -v xdg-open > /dev/null 2>&1; then
|
||||||
xdg-open 'unityhub://v1/editor-signin' >/dev/null 2>&1 || true
|
xdg-open 'unityhub://v1/editor-signin' > /dev/null 2>&1 || true
|
||||||
log_ok "Test link invoked. Check if Unity Hub launches or focuses."
|
log_ok "Test link invoked. Check if Unity Hub launches or focuses."
|
||||||
else
|
else
|
||||||
log_warn "xdg-open not found; cannot run test automatically."
|
log_warn "xdg-open not found; cannot run test automatically."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_info "You can test manually with: xdg-open 'unityhub://v1/editor-signin'"
|
log_info "You can test manually with: xdg-open 'unityhub://v1/editor-signin'"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
log_info "Ensuring required tools (optional)."
|
log_info "Ensuring required tools (optional)."
|
||||||
ensure_deps_arch
|
ensure_deps_arch
|
||||||
|
|
||||||
log_info "Detecting Unity Hub installation..."
|
log_info "Detecting Unity Hub installation..."
|
||||||
IFS='|' read -r install_type exec_cmd < <(detect_unityhub)
|
IFS='|' read -r install_type exec_cmd < <(detect_unityhub)
|
||||||
log_info "Detected type: $install_type"
|
log_info "Detected type: $install_type"
|
||||||
if [[ -z ${exec_cmd:-} ]]; then
|
if [[ -z ${exec_cmd:-} ]]; then
|
||||||
log_warn "Could not find Unity Hub executable automatically."
|
log_warn "Could not find Unity Hub executable automatically."
|
||||||
log_warn "- If using Flatpak: install with 'flatpak install flathub com.unity.UnityHub'"
|
log_warn "- If using Flatpak: install with 'flatpak install flathub com.unity.UnityHub'"
|
||||||
log_warn "- If native (AUR): ensure 'unityhub' is in PATH"
|
log_warn "- If native (AUR): ensure 'unityhub' is in PATH"
|
||||||
log_warn "- If AppImage: place it in ~/Applications and make it executable"
|
log_warn "- If AppImage: place it in ~/Applications and make it executable"
|
||||||
log_error "Aborting—no Exec command available to create handler."
|
log_error "Aborting—no Exec command available to create handler."
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
log_info "Using Exec: $exec_cmd"
|
log_info "Using Exec: $exec_cmd"
|
||||||
|
|
||||||
local desktop_file
|
local desktop_file
|
||||||
desktop_file="$(create_handler_desktop "$exec_cmd")"
|
desktop_file="$(create_handler_desktop "$exec_cmd")"
|
||||||
|
|
||||||
register_mime_handler "$desktop_file"
|
register_mime_handler "$desktop_file"
|
||||||
verify_registration "$desktop_file"
|
verify_registration "$desktop_file"
|
||||||
|
|
||||||
cat <<'NOTE'
|
cat << 'NOTE'
|
||||||
---
|
---
|
||||||
Next steps:
|
Next steps:
|
||||||
- Sign in from Unity Hub. When the browser finishes, ALLOW the prompt to open xdg-open/Unity Hub.
|
- Sign in from Unity Hub. When the browser finishes, ALLOW the prompt to open xdg-open/Unity Hub.
|
||||||
@ -294,9 +294,9 @@ Next steps:
|
|||||||
---
|
---
|
||||||
NOTE
|
NOTE
|
||||||
|
|
||||||
maybe_test_open
|
maybe_test_open
|
||||||
|
|
||||||
log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R \"x-scheme-handler/unityhub\" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10"
|
log_ok "Done. If login still fails, check the Hub's logs and share the outputs of:\n which unityhub || true\n flatpak info com.unity.UnityHub 2>/dev/null | sed -n '1,5p' || true\n xdg-mime query default x-scheme-handler/unityhub\n grep -R \"x-scheme-handler/unityhub\" ~/.local/share/applications /usr/share/applications 2>/dev/null | head -n 10"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -18,16 +18,16 @@ source "$SCRIPT_DIR/../../lib/common.sh"
|
|||||||
|
|
||||||
YES=false
|
YES=false
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-y | --yes)
|
-y | --yes)
|
||||||
YES=true
|
YES=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown option: $1" >&2
|
echo "Unknown option: $1" >&2
|
||||||
exit 2
|
exit 2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
RN_TARGET_DIR=${RN_TARGET_DIR:-"$(dirname "$0")/models"}
|
RN_TARGET_DIR=${RN_TARGET_DIR:-"$(dirname "$0")/models"}
|
||||||
@ -37,125 +37,125 @@ mkdir -p "$RN_TARGET_DIR"
|
|||||||
dest="$RN_TARGET_DIR/$RN_TARGET_NAME"
|
dest="$RN_TARGET_DIR/$RN_TARGET_NAME"
|
||||||
|
|
||||||
if [[ -f $dest ]]; then
|
if [[ -f $dest ]]; then
|
||||||
echo "Model already exists at: $dest"
|
echo "Model already exists at: $dest"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! $YES; then
|
if ! $YES; then
|
||||||
if ! ask_yes_no "Download RNNoise model to $dest?"; then
|
if ! ask_yes_no "Download RNNoise model to $dest?"; then
|
||||||
echo "Aborted."
|
echo "Aborted."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! has_cmd curl && ! has_cmd wget; then
|
if ! has_cmd curl && ! has_cmd wget; then
|
||||||
echo "Error: Need curl or wget to download RNNoise model." >&2
|
echo "Error: Need curl or wget to download RNNoise model." >&2
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Helper: try to download a URL to destination, exit 0 on success
|
# Helper: try to download a URL to destination, exit 0 on success
|
||||||
# Usage: try_download_model URL DEST
|
# Usage: try_download_model URL DEST
|
||||||
try_download_model() {
|
try_download_model() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local dest="$2"
|
local dest="$2"
|
||||||
local tmp
|
local tmp
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
echo "Attempting to download RNNoise model from: $url" >&2
|
echo "Attempting to download RNNoise model from: $url" >&2
|
||||||
if has_cmd curl; then
|
if has_cmd curl; then
|
||||||
curl -fsSL "$url" -o "$tmp" 2>/dev/null || true
|
curl -fsSL "$url" -o "$tmp" 2> /dev/null || true
|
||||||
else
|
else
|
||||||
wget -qO "$tmp" "$url" 2>/dev/null || true
|
wget -qO "$tmp" "$url" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -s $tmp ]]; then
|
if [[ -s $tmp ]]; then
|
||||||
mv "$tmp" "$dest"
|
mv "$tmp" "$dest"
|
||||||
echo "Saved RNNoise model to: $dest" >&2
|
echo "Saved RNNoise model to: $dest" >&2
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
rm -f "$tmp" || true
|
rm -f "$tmp" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Priority 1: explicit URL
|
# Priority 1: explicit URL
|
||||||
if [[ -n ${RN_URL:-} ]]; then
|
if [[ -n ${RN_URL:-} ]]; then
|
||||||
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
||||||
try_download_model "$RN_URL" "$dest"
|
try_download_model "$RN_URL" "$dest"
|
||||||
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Priority 2: rnnoise-nu known models (GregorR)
|
# Priority 2: rnnoise-nu known models (GregorR)
|
||||||
NU_URLS=(
|
NU_URLS=(
|
||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/sh.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/sh.rnnn"
|
||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/lq.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/lq.rnnn"
|
||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/mp.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/mp.rnnn"
|
||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/bd.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/bd.rnnn"
|
||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn"
|
||||||
)
|
)
|
||||||
for u in "${NU_URLS[@]}"; do
|
for u in "${NU_URLS[@]}"; do
|
||||||
try_download_model "$u" "$dest"
|
try_download_model "$u" "$dest"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Priority 2b: arnndn-models fallback (richardpl)
|
# Priority 2b: arnndn-models fallback (richardpl)
|
||||||
RNNDN_URLS=(
|
RNNDN_URLS=(
|
||||||
"https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn"
|
"https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn"
|
||||||
)
|
)
|
||||||
for u in "${RNNDN_URLS[@]}"; do
|
for u in "${RNNDN_URLS[@]}"; do
|
||||||
try_download_model "$u" "$dest"
|
try_download_model "$u" "$dest"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Priority 3: repo archives (rnnoise-nu and arnndn-models)
|
# Priority 3: repo archives (rnnoise-nu and arnndn-models)
|
||||||
ARCHIVES=(
|
ARCHIVES=(
|
||||||
"https://github.com/GregorR/rnnoise-nu/archive/refs/heads/master.zip"
|
"https://github.com/GregorR/rnnoise-nu/archive/refs/heads/master.zip"
|
||||||
"https://github.com/richardpl/arnndn-models/archive/refs/heads/master.zip"
|
"https://github.com/richardpl/arnndn-models/archive/refs/heads/master.zip"
|
||||||
)
|
)
|
||||||
for aurl in "${ARCHIVES[@]}"; do
|
for aurl in "${ARCHIVES[@]}"; do
|
||||||
echo "Attempting to download archive: $aurl" >&2
|
echo "Attempting to download archive: $aurl" >&2
|
||||||
tmpdir=$(mktemp -d)
|
tmpdir=$(mktemp -d)
|
||||||
archive="$tmpdir/models.zip"
|
archive="$tmpdir/models.zip"
|
||||||
set +e
|
set +e
|
||||||
if has_cmd curl; then
|
if has_cmd curl; then
|
||||||
curl -fL "$aurl" -o "$archive"
|
curl -fL "$aurl" -o "$archive"
|
||||||
else
|
else
|
||||||
wget -O "$archive" "$aurl"
|
wget -O "$archive" "$aurl"
|
||||||
fi
|
fi
|
||||||
status=$?
|
status=$?
|
||||||
set -e
|
set -e
|
||||||
if [[ $status -ne 0 ]]; then
|
if [[ $status -ne 0 ]]; then
|
||||||
rm -rf "$tmpdir" || true
|
rm -rf "$tmpdir" || true
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
if has_cmd bsdtar; then
|
if has_cmd bsdtar; then
|
||||||
bsdtar -xf "$archive" -C "$tmpdir"
|
bsdtar -xf "$archive" -C "$tmpdir"
|
||||||
elif has_cmd unzip; then
|
elif has_cmd unzip; then
|
||||||
unzip -q "$archive" -d "$tmpdir"
|
unzip -q "$archive" -d "$tmpdir"
|
||||||
else
|
else
|
||||||
echo "Warning: Need bsdtar or unzip to extract archive; skipping archive method." >&2
|
echo "Warning: Need bsdtar or unzip to extract archive; skipping archive method." >&2
|
||||||
rm -rf "$tmpdir" || true
|
rm -rf "$tmpdir" || true
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
mapfile -t nnfiles < <(bash -lc 'shopt -s globstar nullglob; for f in '"$tmpdir"'/**/*.rnnn '"$tmpdir"'/**/*.nn; do [[ -f "$f" ]] && echo "$f"; done')
|
mapfile -t nnfiles < <(bash -lc 'shopt -s globstar nullglob; for f in '"$tmpdir"'/**/*.rnnn '"$tmpdir"'/**/*.nn; do [[ -f "$f" ]] && echo "$f"; done')
|
||||||
if [[ ${#nnfiles[@]} -gt 0 ]]; then
|
if [[ ${#nnfiles[@]} -gt 0 ]]; then
|
||||||
cp -f "${nnfiles[0]}" "$dest"
|
cp -f "${nnfiles[0]}" "$dest"
|
||||||
echo "Saved RNNoise model to: $dest (from archive)" >&2
|
echo "Saved RNNoise model to: $dest (from archive)" >&2
|
||||||
rm -rf "$tmpdir" || true
|
rm -rf "$tmpdir" || true
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
rm -rf "$tmpdir" || true
|
rm -rf "$tmpdir" || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Priority 4: Arch-based AUR packages and search only .nn/.rnnn
|
# Priority 4: Arch-based AUR packages and search only .nn/.rnnn
|
||||||
if has_cmd yay; then
|
if has_cmd yay; then
|
||||||
echo "Attempting to install AUR packages that may include RNNoise models..." >&2
|
echo "Attempting to install AUR packages that may include RNNoise models..." >&2
|
||||||
set +e
|
set +e
|
||||||
yay -S --noconfirm denoiseit-git 2>/dev/null
|
yay -S --noconfirm denoiseit-git 2> /dev/null
|
||||||
yay -S --noconfirm speech-denoiser-git 2>/dev/null
|
yay -S --noconfirm speech-denoiser-git 2> /dev/null
|
||||||
set -e
|
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
|
if [[ ${#found[@]} -gt 0 ]]; then
|
||||||
echo "Found candidate models:" >&2
|
echo "Found candidate models:" >&2
|
||||||
printf ' %s\n' "${found[@]}" >&2
|
printf ' %s\n' "${found[@]}" >&2
|
||||||
cp -f "${found[0]}" "$dest"
|
cp -f "${found[0]}" "$dest"
|
||||||
echo "Copied model to: $dest" >&2
|
echo "Copied model to: $dest" >&2
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Error: Could not obtain an RNNoise model automatically." >&2
|
echo "Error: Could not obtain an RNNoise model automatically." >&2
|
||||||
|
|||||||
@ -13,113 +13,113 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|||||||
source "$SCRIPT_DIR/../../lib/common.sh"
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
|
|
||||||
print_info() {
|
print_info() {
|
||||||
echo "[info] $*"
|
echo "[info] $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_distro() {
|
detect_distro() {
|
||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
echo "${ID:-unknown}"
|
echo "${ID:-unknown}"
|
||||||
else
|
else
|
||||||
echo "unknown"
|
echo "unknown"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
local distro
|
local distro
|
||||||
distro=$(detect_distro)
|
distro=$(detect_distro)
|
||||||
print_info "Detected distro: $distro"
|
print_info "Detected distro: $distro"
|
||||||
|
|
||||||
if has_cmd ffmpeg && ffmpeg -hide_banner -filters | grep -q " arnndn "; then
|
if has_cmd ffmpeg && ffmpeg -hide_banner -filters | grep -q " arnndn "; then
|
||||||
print_info "Your ffmpeg already supports arnndn."
|
print_info "Your ffmpeg already supports arnndn."
|
||||||
else
|
else
|
||||||
case "$distro" in
|
case "$distro" in
|
||||||
ubuntu | debian)
|
ubuntu | debian)
|
||||||
print_info "On Ubuntu/Debian, the official repo may lack newer filters. Consider a PPA or build from source."
|
print_info "On Ubuntu/Debian, the official repo may lack newer filters. Consider a PPA or build from source."
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " - ppa: sudo add-apt-repository ppa:savoury1/ffmpeg6 && sudo apt update && sudo apt install ffmpeg"
|
echo " - ppa: sudo add-apt-repository ppa:savoury1/ffmpeg6 && sudo apt update && sudo apt install ffmpeg"
|
||||||
echo " - source build (recommended for latest): run this script to build from source"
|
echo " - source build (recommended for latest): run this script to build from source"
|
||||||
;;
|
;;
|
||||||
arch | manjaro | endeavouros)
|
arch | manjaro | endeavouros)
|
||||||
print_info "On Arch-based distros, ffmpeg is recent. Try: sudo pacman -Syu ffmpeg"
|
print_info "On Arch-based distros, ffmpeg is recent. Try: sudo pacman -Syu ffmpeg"
|
||||||
;;
|
;;
|
||||||
fedora)
|
fedora)
|
||||||
print_info "On Fedora, try: sudo dnf install ffmpeg"
|
print_info "On Fedora, try: sudo dnf install ffmpeg"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
print_info "Distro not recognized; will offer source build."
|
print_info "Distro not recognized; will offer source build."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ask_yes_no "Build FFmpeg from source with rnnoise/arnndn support now?"; then
|
if ask_yes_no "Build FFmpeg from source with rnnoise/arnndn support now?"; then
|
||||||
echo "This will clone FFmpeg and build locally under ./ffmpeg-build. Continue?"
|
echo "This will clone FFmpeg and build locally under ./ffmpeg-build. Continue?"
|
||||||
if ! ask_yes_no "Proceed"; then
|
if ! ask_yes_no "Proceed"; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
set -x
|
set -x
|
||||||
mkdir -p ffmpeg-build && cd ffmpeg-build
|
mkdir -p ffmpeg-build && cd ffmpeg-build
|
||||||
# Prepare repository
|
# Prepare repository
|
||||||
if [[ -d FFmpeg ]]; then
|
if [[ -d FFmpeg ]]; then
|
||||||
if [[ -d FFmpeg/.git ]]; then
|
if [[ -d FFmpeg/.git ]]; then
|
||||||
if ask_yes_no "An existing FFmpeg source directory was found. Reuse and update it?"; then
|
if ask_yes_no "An existing FFmpeg source directory was found. Reuse and update it?"; then
|
||||||
set +e
|
set +e
|
||||||
git -C FFmpeg fetch --all --tags --prune
|
git -C FFmpeg fetch --all --tags --prune
|
||||||
git -C FFmpeg pull --rebase --ff-only || true
|
git -C FFmpeg pull --rebase --ff-only || true
|
||||||
set -e
|
set -e
|
||||||
else
|
else
|
||||||
if ask_yes_no "Delete existing FFmpeg directory and re-clone?"; then
|
if ask_yes_no "Delete existing FFmpeg directory and re-clone?"; then
|
||||||
rm -rf FFmpeg
|
rm -rf FFmpeg
|
||||||
else
|
else
|
||||||
echo "Keeping existing FFmpeg directory as-is."
|
echo "Keeping existing FFmpeg directory as-is."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if ask_yes_no "Non-git 'FFmpeg' directory exists. Delete and re-clone?"; then
|
if ask_yes_no "Non-git 'FFmpeg' directory exists. Delete and re-clone?"; then
|
||||||
rm -rf FFmpeg
|
rm -rf FFmpeg
|
||||||
else
|
else
|
||||||
echo "Cannot proceed with a non-git FFmpeg directory present. Aborting."
|
echo "Cannot proceed with a non-git FFmpeg directory present. Aborting."
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
# Dependencies
|
# Dependencies
|
||||||
if [[ $distro == "ubuntu" || $distro == "debian" ]]; then
|
if [[ $distro == "ubuntu" || $distro == "debian" ]]; then
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev
|
sudo apt install -y git build-essential yasm nasm pkg-config libx264-dev libx265-dev libvpx-dev libopus-dev libfdk-aac-dev libmp3lame-dev libvorbis-dev libass-dev libfreetype6-dev libgnutls28-dev libaom-dev libdav1d-dev libxvidcore-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev libxcb-shape0-dev libdrm-dev libvulkan-dev libva-dev libvdpau-dev librtmp-dev libunistring-dev libgnutls28-dev libchromaprint-dev libbluray-dev librubberband-dev libspeex-dev libsoxr-dev libvmaf-dev libzimg-dev libsvtav1-dev libtheora-dev libwebp-dev libopenal-dev libjack-jackd2-dev libpulse-dev librnnoise-dev
|
||||||
elif [[ $distro == "arch" || $distro == "manjaro" || $distro == "endeavouros" ]]; then
|
elif [[ $distro == "arch" || $distro == "manjaro" || $distro == "endeavouros" ]]; then
|
||||||
sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise
|
sudo pacman -Syu --needed base-devel yasm nasm pkgconf rnnoise
|
||||||
elif [[ $distro == "fedora" ]]; then
|
elif [[ $distro == "fedora" ]]; then
|
||||||
sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel
|
sudo dnf install -y git make gcc yasm nasm pkgconf-pkg-config rnnoise-devel libX11-devel libXext-devel libXfixes-devel libXv-devel libXrandr-devel libXi-devel libXtst-devel libXinerama-devel freetype-devel fontconfig-devel libass-devel libvpx-devel libaom-devel libdav1d-devel zimg-devel rubberband-devel soxr-devel libvorbis-devel opus-devel lame-devel
|
||||||
else
|
else
|
||||||
echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2
|
echo "Note: please ensure rnnoise development headers are installed (pkg-config rnnoise)." >&2
|
||||||
fi
|
fi
|
||||||
if [[ ! -d FFmpeg/.git ]]; then
|
if [[ ! -d FFmpeg/.git ]]; then
|
||||||
git clone https://github.com/FFmpeg/FFmpeg.git --depth=1
|
git clone https://github.com/FFmpeg/FFmpeg.git --depth=1
|
||||||
fi
|
fi
|
||||||
cd FFmpeg
|
cd FFmpeg
|
||||||
RN_FLAG=""
|
RN_FLAG=""
|
||||||
# Some FFmpeg versions auto-detect rnnoise without a flag; include the flag only if supported
|
# Some FFmpeg versions auto-detect rnnoise without a flag; include the flag only if supported
|
||||||
if ./configure --help | grep -q "librnnoise"; then
|
if ./configure --help | grep -q "librnnoise"; then
|
||||||
RN_FLAG="--enable-librnnoise"
|
RN_FLAG="--enable-librnnoise"
|
||||||
else
|
else
|
||||||
echo "[info] configure has no --enable-librnnoise; relying on auto-detection via pkg-config (rnnoise)." >&2
|
echo "[info] configure has no --enable-librnnoise; relying on auto-detection via pkg-config (rnnoise)." >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
./configure \
|
./configure \
|
||||||
--enable-gpl --enable-nonfree \
|
--enable-gpl --enable-nonfree \
|
||||||
--enable-libx264 --enable-libx265 --enable-libvpx --enable-libopus --enable-libmp3lame \
|
--enable-libx264 --enable-libx265 --enable-libvpx --enable-libopus --enable-libmp3lame \
|
||||||
--enable-libvorbis --enable-libass --enable-fontconfig --enable-libfreetype \
|
--enable-libvorbis --enable-libass --enable-fontconfig --enable-libfreetype \
|
||||||
--enable-librubberband --enable-libsoxr --enable-libzimg --enable-libvmaf \
|
--enable-librubberband --enable-libsoxr --enable-libzimg --enable-libvmaf \
|
||||||
--enable-libdav1d --enable-libaom --enable-libsvtav1 \
|
--enable-libdav1d --enable-libaom --enable-libsvtav1 \
|
||||||
${RN_FLAG} \
|
${RN_FLAG} \
|
||||||
--enable-ffplay --enable-ffprobe
|
--enable-ffplay --enable-ffprobe
|
||||||
make -j"$(nproc)"
|
make -j"$(nproc)"
|
||||||
echo "Build complete. You can run ./ffmpeg-build/FFmpeg/ffmpeg from this folder or 'sudo make install' to install system-wide."
|
echo "Build complete. You can run ./ffmpeg-build/FFmpeg/ffmpeg from this folder or 'sudo make install' to install system-wide."
|
||||||
set +x
|
set +x
|
||||||
else
|
else
|
||||||
echo "Skipped building from source."
|
echo "Skipped building from source."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -15,158 +15,158 @@ BLUE="\033[34m"
|
|||||||
RESET="\033[0m"
|
RESET="\033[0m"
|
||||||
|
|
||||||
info() {
|
info() {
|
||||||
printf "%b[%s]%b %s\n" "$BLUE" "$SCRIPT_NAME" "$RESET" "$*"
|
printf "%b[%s]%b %s\n" "$BLUE" "$SCRIPT_NAME" "$RESET" "$*"
|
||||||
}
|
}
|
||||||
|
|
||||||
warn() {
|
warn() {
|
||||||
printf "%b[%s]%b %s\n" "$YELLOW" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
printf "%b[%s]%b %s\n" "$YELLOW" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
error() {
|
error() {
|
||||||
printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_pacman_packages() {
|
ensure_pacman_packages() {
|
||||||
install_missing_pacman_packages python git curl jq code
|
install_missing_pacman_packages python git curl jq code
|
||||||
}
|
}
|
||||||
|
|
||||||
install_uv() {
|
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."
|
info "uv is already installed."
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Installing uv toolchain manager via official installer."
|
info "Installing uv toolchain manager via official installer."
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
local local_bin="$HOME/.local/bin"
|
local local_bin="$HOME/.local/bin"
|
||||||
if [[ :$PATH: != *":$local_bin:"* ]]; then
|
if [[ :$PATH: != *":$local_bin:"* ]]; then
|
||||||
warn "Adding $local_bin to PATH in ~/.profile and ~/.zshrc. Open a new shell to apply."
|
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/.profile"
|
||||||
printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >>"$HOME/.zshrc"
|
printf "\nexport PATH=\"\$HOME/.local/bin:\$PATH\"\n" >> "$HOME/.zshrc"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_unity_hub() {
|
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."
|
info "Unity Hub already installed."
|
||||||
return
|
return
|
||||||
fi
|
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."
|
info "Installing Unity Hub from AUR using yay."
|
||||||
yay -S --needed --noconfirm unityhub
|
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."
|
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"
|
flatpak install -y com.unity.UnityHub || warn "Flatpak installation failed. Install Unity Hub manually via https://unity.com/download"
|
||||||
else
|
else
|
||||||
warn "Unity Hub not found and neither yay nor flatpak is available. Install Unity Hub manually from https://unity.com/download."
|
warn "Unity Hub not found and neither yay nor flatpak is available. Install Unity Hub manually from https://unity.com/download."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
sync_unity_mcp_repo() {
|
sync_unity_mcp_repo() {
|
||||||
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||||
local unity_mcp_root="$data_home/UnityMCP"
|
local unity_mcp_root="$data_home/UnityMCP"
|
||||||
local repo_dir="$unity_mcp_root/unity-mcp-repo"
|
local repo_dir="$unity_mcp_root/unity-mcp-repo"
|
||||||
local server_link="$unity_mcp_root/UnityMcpServer"
|
local server_link="$unity_mcp_root/UnityMcpServer"
|
||||||
local candidates=(
|
local candidates=(
|
||||||
"UnityMcpServer"
|
"UnityMcpServer"
|
||||||
"UnityMcpBridge/UnityMcpServer"
|
"UnityMcpBridge/UnityMcpServer"
|
||||||
"UnityMcpBridge/UnityMcpServer~"
|
"UnityMcpBridge/UnityMcpServer~"
|
||||||
)
|
)
|
||||||
local server_subdir=""
|
local server_subdir=""
|
||||||
|
|
||||||
mkdir -p "$unity_mcp_root"
|
mkdir -p "$unity_mcp_root"
|
||||||
|
|
||||||
if [[ -d "$repo_dir/.git" ]]; then
|
if [[ -d "$repo_dir/.git" ]]; then
|
||||||
info "Updating existing unity-mcp repository."
|
info "Updating existing unity-mcp repository."
|
||||||
git -C "$repo_dir" pull --ff-only
|
git -C "$repo_dir" pull --ff-only
|
||||||
else
|
else
|
||||||
info "Cloning unity-mcp repository."
|
info "Cloning unity-mcp repository."
|
||||||
rm -rf "$repo_dir"
|
rm -rf "$repo_dir"
|
||||||
git clone --depth=1 https://github.com/CoplayDev/unity-mcp.git "$repo_dir"
|
git clone --depth=1 https://github.com/CoplayDev/unity-mcp.git "$repo_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for candidate in "${candidates[@]}"; do
|
for candidate in "${candidates[@]}"; do
|
||||||
if [[ -d "$repo_dir/$candidate/src" ]]; then
|
if [[ -d "$repo_dir/$candidate/src" ]]; then
|
||||||
server_subdir="$candidate"
|
server_subdir="$candidate"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z $server_subdir ]]; then
|
if [[ -z $server_subdir ]]; then
|
||||||
error "UnityMcpServer src directory not found. Checked candidates: ${candidates[*]}"
|
error "UnityMcpServer src directory not found. Checked candidates: ${candidates[*]}"
|
||||||
error "Repository layout may have changed. Inspect $repo_dir for the new server location."
|
error "Repository layout may have changed. Inspect $repo_dir for the new server location."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ln -sfn "$repo_dir/$server_subdir" "$server_link"
|
ln -sfn "$repo_dir/$server_subdir" "$server_link"
|
||||||
info "UnityMcpServer synchronized at $server_link (source: $server_subdir)"
|
info "UnityMcpServer synchronized at $server_link (source: $server_subdir)"
|
||||||
}
|
}
|
||||||
|
|
||||||
configure_vscode_mcp() {
|
configure_vscode_mcp() {
|
||||||
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
local data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||||
local server_src="$data_home/UnityMCP/UnityMcpServer/src"
|
local server_src="$data_home/UnityMCP/UnityMcpServer/src"
|
||||||
local mcp_config_dir="$HOME/.config/Code/User"
|
local mcp_config_dir="$HOME/.config/Code/User"
|
||||||
local mcp_config="$mcp_config_dir/mcp.json"
|
local mcp_config="$mcp_config_dir/mcp.json"
|
||||||
local tmp
|
local tmp
|
||||||
|
|
||||||
if [[ ! -d $server_src ]]; then
|
if [[ ! -d $server_src ]]; then
|
||||||
error "Server source directory $server_src is missing."
|
error "Server source directory $server_src is missing."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$mcp_config_dir"
|
mkdir -p "$mcp_config_dir"
|
||||||
|
|
||||||
if [[ ! -f $mcp_config ]]; then
|
if [[ ! -f $mcp_config ]]; then
|
||||||
info "Creating new VS Code MCP configuration at $mcp_config"
|
info "Creating new VS Code MCP configuration at $mcp_config"
|
||||||
echo '{}' >"$mcp_config"
|
echo '{}' > "$mcp_config"
|
||||||
else
|
else
|
||||||
info "Updating existing VS Code MCP configuration at $mcp_config"
|
info "Updating existing VS Code MCP configuration at $mcp_config"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
tmp="$(mktemp)"
|
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."
|
error "Existing $mcp_config is not valid JSON. Please fix it before running this script again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
jq \
|
jq \
|
||||||
--arg path "$server_src" \
|
--arg path "$server_src" \
|
||||||
'(.servers //= {}) |
|
'(.servers //= {}) |
|
||||||
.servers.unityMCP = {
|
.servers.unityMCP = {
|
||||||
command: "uv",
|
command: "uv",
|
||||||
args: ["--directory", $path, "run", "server.py"],
|
args: ["--directory", $path, "run", "server.py"],
|
||||||
type: "stdio"
|
type: "stdio"
|
||||||
}' \
|
}' \
|
||||||
"$mcp_config" >"$tmp"
|
"$mcp_config" > "$tmp"
|
||||||
|
|
||||||
mv "$tmp" "$mcp_config"
|
mv "$tmp" "$mcp_config"
|
||||||
info "VS Code MCP server configuration updated for UnityMCP."
|
info "VS Code MCP server configuration updated for UnityMCP."
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_python_version() {
|
verify_python_version() {
|
||||||
|
|
||||||
require_command python "python"
|
require_command python "python"
|
||||||
local version
|
local version
|
||||||
version="$(
|
version="$(
|
||||||
python - <<'PY'
|
python - << 'PY'
|
||||||
import sys
|
import sys
|
||||||
print("%d.%d.%d" % sys.version_info[:3])
|
print("%d.%d.%d" % sys.version_info[:3])
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
local major minor
|
local major minor
|
||||||
IFS='.' read -r major minor _ <<<"$version"
|
IFS='.' read -r major minor _ <<< "$version"
|
||||||
if ((major < 3 || (major == 3 && minor < 12))); then
|
if ((major < 3 || (major == 3 && minor < 12))); then
|
||||||
error "Python 3.12+ is required. Detected version $version. Upgrade python before continuing."
|
error "Python 3.12+ is required. Detected version $version. Upgrade python before continuing."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
info "Python version $version satisfies requirement (>= 3.12)."
|
info "Python version $version satisfies requirement (>= 3.12)."
|
||||||
}
|
}
|
||||||
|
|
||||||
print_next_steps() {
|
print_next_steps() {
|
||||||
cat <<'EOT'
|
cat << 'EOT'
|
||||||
|
|
||||||
Next steps:
|
Next steps:
|
||||||
1. Launch Unity Hub and install a Unity Editor version 2021.3 LTS or newer.
|
1. Launch Unity Hub and install a Unity Editor version 2021.3 LTS or newer.
|
||||||
@ -190,22 +190,22 @@ EOT
|
|||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
if [[ ! -f /etc/arch-release ]]; then
|
if [[ ! -f /etc/arch-release ]]; then
|
||||||
error "This script is intended for Arch Linux."
|
error "This script is intended for Arch Linux."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Ensuring base dependencies are installed."
|
info "Ensuring base dependencies are installed."
|
||||||
require_command sudo "sudo"
|
require_command sudo "sudo"
|
||||||
require_command pacman "pacman"
|
require_command pacman "pacman"
|
||||||
ensure_pacman_packages
|
ensure_pacman_packages
|
||||||
verify_python_version
|
verify_python_version
|
||||||
install_uv
|
install_uv
|
||||||
ensure_unity_hub
|
ensure_unity_hub
|
||||||
sync_unity_mcp_repo
|
sync_unity_mcp_repo
|
||||||
configure_vscode_mcp
|
configure_vscode_mcp
|
||||||
print_next_steps
|
print_next_steps
|
||||||
info "Setup complete. Follow the next steps above to finish configuration inside Unity."
|
info "Setup complete. Follow the next steps above to finish configuration inside Unity."
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -44,19 +44,19 @@ DEBUG=0
|
|||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
if [[ -t 1 && ${NO_COLOR} -eq 0 ]]; then
|
if [[ -t 1 && ${NO_COLOR} -eq 0 ]]; then
|
||||||
GREEN="\e[32m"
|
GREEN="\e[32m"
|
||||||
YELLOW="\e[33m"
|
YELLOW="\e[33m"
|
||||||
RED="\e[31m"
|
RED="\e[31m"
|
||||||
BLUE="\e[34m"
|
BLUE="\e[34m"
|
||||||
BOLD="\e[1m"
|
BOLD="\e[1m"
|
||||||
RESET="\e[0m"
|
RESET="\e[0m"
|
||||||
else
|
else
|
||||||
GREEN=""
|
GREEN=""
|
||||||
YELLOW=""
|
YELLOW=""
|
||||||
RED=""
|
RED=""
|
||||||
BLUE=""
|
BLUE=""
|
||||||
BOLD=""
|
BOLD=""
|
||||||
RESET=""
|
RESET=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
|
||||||
@ -65,7 +65,7 @@ err() { echo -e "${RED}[ERR ]${RESET} $*" >&2; }
|
|||||||
success() { echo -e "${GREEN}[OK ]${RESET} $*"; }
|
success() { echo -e "${GREEN}[OK ]${RESET} $*"; }
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
${SCRIPT_NAME} v${VERSION}
|
${SCRIPT_NAME} v${VERSION}
|
||||||
Setup or uninstall a self-hosted LibreTranslate instance via Docker.
|
Setup or uninstall a self-hosted LibreTranslate instance via Docker.
|
||||||
|
|
||||||
@ -111,378 +111,378 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
gen_api_key() {
|
gen_api_key() {
|
||||||
# Avoid SIGPIPE issues under set -o pipefail by capturing output first
|
# Avoid SIGPIPE issues under set -o pipefail by capturing output first
|
||||||
local key
|
local key
|
||||||
key=$(head -c 256 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 40 || true)
|
key=$(head -c 256 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 40 || true)
|
||||||
if [[ -z $key || ${#key} -lt 40 ]]; then
|
if [[ -z $key || ${#key} -lt 40 ]]; then
|
||||||
# Fallback using openssl if available
|
# 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)
|
key=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 40 || true)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ -z $key || ${#key} -lt 20 ]]; then
|
if [[ -z $key || ${#key} -lt 20 ]]; then
|
||||||
# Last resort static warning key (should not happen)
|
# Last resort static warning key (should not happen)
|
||||||
key="LT$(date +%s)$$RANDOM"
|
key="LT$(date +%s)$$RANDOM"
|
||||||
fi
|
fi
|
||||||
printf '%s' "$key"
|
printf '%s' "$key"
|
||||||
}
|
}
|
||||||
|
|
||||||
need_cmd() {
|
need_cmd() {
|
||||||
command -v "$1" >/dev/null 2>&1 || {
|
command -v "$1" > /dev/null 2>&1 || {
|
||||||
err "Required command '$1' not found"
|
err "Required command '$1' not found"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--image)
|
--image)
|
||||||
IMAGE="$2"
|
IMAGE="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--tag)
|
--tag)
|
||||||
TAG="$2"
|
TAG="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--port)
|
--port)
|
||||||
PORT="$2"
|
PORT="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--host)
|
--host)
|
||||||
HOST="$2"
|
HOST="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--data-dir)
|
--data-dir)
|
||||||
DATA_DIR="$2"
|
DATA_DIR="$2"
|
||||||
CACHE_DIR="${DATA_DIR}/cache"
|
CACHE_DIR="${DATA_DIR}/cache"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--cache-dir)
|
--cache-dir)
|
||||||
CACHE_DIR="$2"
|
CACHE_DIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--no-docker-install)
|
--no-docker-install)
|
||||||
DOCKER_INSTALL=0
|
DOCKER_INSTALL=0
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--keep-alive)
|
--keep-alive)
|
||||||
KEEP_ALIVE=1
|
KEEP_ALIVE=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
shift
|
shift
|
||||||
RUN_COMMAND=("$@")
|
RUN_COMMAND=("$@")
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
--api-key)
|
--api-key)
|
||||||
API_KEY="$2"
|
API_KEY="$2"
|
||||||
GENERATE_API_KEY=0
|
GENERATE_API_KEY=0
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--generate-api-key)
|
--generate-api-key)
|
||||||
GENERATE_API_KEY=1
|
GENERATE_API_KEY=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--disable-api-key)
|
--disable-api-key)
|
||||||
DISABLE_API_KEY=1
|
DISABLE_API_KEY=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--preload-langs)
|
--preload-langs)
|
||||||
PRELOAD_LANGS="$2"
|
PRELOAD_LANGS="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--env)
|
--env)
|
||||||
EXTRA_ENV+=("$2")
|
EXTRA_ENV+=("$2")
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--pull-only)
|
--pull-only)
|
||||||
PULL_ONLY=1
|
PULL_ONLY=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--uninstall)
|
--uninstall)
|
||||||
UNINSTALL=1
|
UNINSTALL=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--purge)
|
--purge)
|
||||||
UNINSTALL=1
|
UNINSTALL=1
|
||||||
KEEP_DATA=0
|
KEEP_DATA=0
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--keep-data)
|
--keep-data)
|
||||||
KEEP_DATA=1
|
KEEP_DATA=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--health-timeout)
|
--health-timeout)
|
||||||
HEALTH_TIMEOUT="$2"
|
HEALTH_TIMEOUT="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--no-color)
|
--no-color)
|
||||||
NO_COLOR=1
|
NO_COLOR=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--debug)
|
--debug)
|
||||||
DEBUG=1
|
DEBUG=1
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
-v | --version)
|
-v | --version)
|
||||||
echo "${VERSION}"
|
echo "${VERSION}"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
err "Unknown argument: $1"
|
err "Unknown argument: $1"
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_root() {
|
ensure_root() {
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
err "This script must run as root (or via sudo)."
|
err "This script must run as root (or via sudo)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_docker() {
|
install_docker() {
|
||||||
if command -v docker >/dev/null 2>&1; then
|
if command -v docker > /dev/null 2>&1; then
|
||||||
log "Docker already installed"
|
log "Docker already installed"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if [[ ${DOCKER_INSTALL} -eq 0 ]]; then
|
if [[ ${DOCKER_INSTALL} -eq 0 ]]; then
|
||||||
err "Docker is not installed and --no-docker-install specified."
|
err "Docker is not installed and --no-docker-install specified."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
log "Installing Docker..."
|
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 update -y
|
||||||
apt-get install -y ca-certificates curl gnupg
|
apt-get install -y ca-certificates curl gnupg
|
||||||
install -d -m 0755 /etc/apt/keyrings
|
install -d -m 0755 /etc/apt/keyrings
|
||||||
curl -fsSL "https://download.docker.com/linux/$(
|
curl -fsSL "https://download.docker.com/linux/$(
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
echo "$ID"
|
echo "$ID"
|
||||||
)/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
)/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||||
echo \
|
echo \
|
||||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
echo "$ID"
|
echo "$ID"
|
||||||
) $(
|
) $(
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
echo "$VERSION_CODENAME"
|
echo "$VERSION_CODENAME"
|
||||||
) stable" \
|
) stable" \
|
||||||
>/etc/apt/sources.list.d/docker.list
|
> /etc/apt/sources.list.d/docker.list
|
||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||||
else
|
else
|
||||||
err "Unsupported package manager. Please install Docker manually."
|
err "Unsupported package manager. Please install Docker manually."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
# Attempt to start docker daemon if dockerd exists and systemctl available; otherwise rely on user
|
# Attempt to start docker daemon if dockerd exists and systemctl available; otherwise rely on user
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
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"
|
(systemctl enable --now docker 2> /dev/null && success "Docker installed and started") || warn "Docker installed; ensure dockerd is running"
|
||||||
else
|
else
|
||||||
warn "Docker installed; please ensure docker daemon is running"
|
warn "Docker installed; please ensure docker daemon is running"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
pull_image() {
|
pull_image() {
|
||||||
log "Pulling image ${IMAGE}:${TAG}"
|
log "Pulling image ${IMAGE}:${TAG}"
|
||||||
docker pull "${IMAGE}:${TAG}"
|
docker pull "${IMAGE}:${TAG}"
|
||||||
success "Image pulled"
|
success "Image pulled"
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_container_user() {
|
detect_container_user() {
|
||||||
# Determine uid/gid of configured user inside image so host dirs can be chowned
|
# 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
|
return 0
|
||||||
fi
|
fi
|
||||||
local uid gid
|
local uid gid
|
||||||
uid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -u 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 "")
|
gid=$(docker run --rm --entrypoint /usr/bin/id "${IMAGE}:${TAG}" -g 2> /dev/null || echo "")
|
||||||
if [[ -n $uid && -n $gid ]]; then
|
if [[ -n $uid && -n $gid ]]; then
|
||||||
CONTAINER_UID=$uid
|
CONTAINER_UID=$uid
|
||||||
CONTAINER_GID=$gid
|
CONTAINER_GID=$gid
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
write_env_file() {
|
write_env_file() {
|
||||||
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${CACHE_DIR}"
|
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${CACHE_DIR}"
|
||||||
detect_container_user
|
detect_container_user
|
||||||
if [[ -n ${CONTAINER_UID:-} && -n ${CONTAINER_GID:-} ]]; then
|
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
|
for d in "${DATA_DIR}" "${CACHE_DIR}"; do
|
||||||
if [[ -d $d ]]; then
|
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
|
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
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ ${DISABLE_API_KEY} -eq 1 ]]; then
|
if [[ ${DISABLE_API_KEY} -eq 1 ]]; then
|
||||||
API_KEY_LINE="LT_NO_API_KEY=true"
|
API_KEY_LINE="LT_NO_API_KEY=true"
|
||||||
else
|
else
|
||||||
if [[ -z ${API_KEY} && ${GENERATE_API_KEY} -eq 1 ]]; then
|
if [[ -z ${API_KEY} && ${GENERATE_API_KEY} -eq 1 ]]; then
|
||||||
API_KEY=$(gen_api_key)
|
API_KEY=$(gen_api_key)
|
||||||
GENERATED=1
|
GENERATED=1
|
||||||
else
|
else
|
||||||
GENERATED=0
|
GENERATED=0
|
||||||
fi
|
fi
|
||||||
API_KEY_LINE="LT_API_KEYS=${API_KEY}"
|
API_KEY_LINE="LT_API_KEYS=${API_KEY}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
echo "# LibreTranslate environment file"
|
echo "# LibreTranslate environment file"
|
||||||
echo "# Generated $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
echo "# Generated $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
echo "${API_KEY_LINE}"
|
echo "${API_KEY_LINE}"
|
||||||
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}"
|
[[ -n ${PRELOAD_LANGS} ]] && echo "LT_PRELOAD_LANGS=${PRELOAD_LANGS}"
|
||||||
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done
|
for kv in "${EXTRA_ENV[@]:-}"; do echo "$kv"; done
|
||||||
} >"${ENV_FILE}.tmp"
|
} > "${ENV_FILE}.tmp"
|
||||||
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
|
mv "${ENV_FILE}.tmp" "${ENV_FILE}"
|
||||||
chmod 600 "${ENV_FILE}"
|
chmod 600 "${ENV_FILE}"
|
||||||
success "Environment file written: ${ENV_FILE}"
|
success "Environment file written: ${ENV_FILE}"
|
||||||
}
|
}
|
||||||
|
|
||||||
start_container_ephemeral() {
|
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}" \
|
docker run -d --name "${SERVICE_NAME}" \
|
||||||
--env-file "${ENV_FILE}" \
|
--env-file "${ENV_FILE}" \
|
||||||
-v "${DATA_DIR}:/home/libretranslate/.local/share/argos-translate" \
|
-v "${DATA_DIR}:/home/libretranslate/.local/share/argos-translate" \
|
||||||
-v "${CACHE_DIR}:/app/cache" \
|
-v "${CACHE_DIR}:/app/cache" \
|
||||||
-p "${PORT}:${PORT}" \
|
-p "${PORT}:${PORT}" \
|
||||||
"${IMAGE}:${TAG}" \
|
"${IMAGE}:${TAG}" \
|
||||||
--host 0.0.0.0 --port "${PORT}"
|
--host 0.0.0.0 --port "${PORT}"
|
||||||
success "Container started (ephemeral)"
|
success "Container started (ephemeral)"
|
||||||
echo
|
echo
|
||||||
echo "Endpoint (pending readiness): http://$(hostname -I | awk '{print $1}'):${PORT}"
|
echo "Endpoint (pending readiness): http://$(hostname -I | awk '{print $1}'):${PORT}"
|
||||||
echo "Waiting for health..."
|
echo "Waiting for health..."
|
||||||
}
|
}
|
||||||
|
|
||||||
health_check() {
|
health_check() {
|
||||||
local start
|
local start
|
||||||
start=$(date +%s)
|
start=$(date +%s)
|
||||||
local url="http://127.0.0.1:${PORT}/languages"
|
local url="http://127.0.0.1:${PORT}/languages"
|
||||||
local attempt=0
|
local attempt=0
|
||||||
while true; do
|
while true; do
|
||||||
attempt=$((attempt + 1))
|
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)"
|
success "Service healthy (attempt $attempt)"
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
[[ $DEBUG -eq 1 ]] && log "Health attempt $attempt failed"
|
[[ $DEBUG -eq 1 ]] && log "Health attempt $attempt failed"
|
||||||
fi
|
fi
|
||||||
if (($(date +%s) - start > HEALTH_TIMEOUT)); then
|
if (($(date +%s) - start > HEALTH_TIMEOUT)); then
|
||||||
err "Health check failed after ${HEALTH_TIMEOUT}s (attempts: $attempt)"
|
err "Health check failed after ${HEALTH_TIMEOUT}s (attempts: $attempt)"
|
||||||
docker logs --tail 200 "${SERVICE_NAME}" || true
|
docker logs --tail 200 "${SERVICE_NAME}" || true
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
sample_request() {
|
sample_request() {
|
||||||
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
||||||
local key="${API_KEY}"
|
local key="${API_KEY}"
|
||||||
else
|
else
|
||||||
local key=""
|
local key=""
|
||||||
fi
|
fi
|
||||||
log "Performing sample translation (en->es)..."
|
log "Performing sample translation (en->es)..."
|
||||||
local DATA='{"q":"Hello world","source":"en","target":"es","format":"text"}'
|
local DATA='{"q":"Hello world","source":"en","target":"es","format":"text"}'
|
||||||
if [[ -n $key ]]; then
|
if [[ -n $key ]]; then
|
||||||
curl -fsS -H "Content-Type: application/json" -H "Authorization: ${key}" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
curl -fsS -H "Content-Type: application/json" -H "Authorization: ${key}" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
||||||
else
|
else
|
||||||
curl -fsS -H "Content-Type: application/json" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
curl -fsS -H "Content-Type: application/json" -d "$DATA" "http://127.0.0.1:${PORT}/translate" || warn "Sample request failed"
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
uninstall_all() {
|
uninstall_all() {
|
||||||
log "Uninstalling LibreTranslate (ephemeral mode)..."
|
log "Uninstalling LibreTranslate (ephemeral mode)..."
|
||||||
docker rm -f "${SERVICE_NAME}" 2>/dev/null || true
|
docker rm -f "${SERVICE_NAME}" 2> /dev/null || true
|
||||||
docker rmi "${IMAGE}:${TAG}" 2>/dev/null || true
|
docker rmi "${IMAGE}:${TAG}" 2> /dev/null || true
|
||||||
if [[ ${KEEP_DATA} -eq 0 ]]; then
|
if [[ ${KEEP_DATA} -eq 0 ]]; then
|
||||||
rm -rf "${DATA_DIR}" "${CONFIG_DIR}" || true
|
rm -rf "${DATA_DIR}" "${CONFIG_DIR}" || true
|
||||||
success "Data directories removed"
|
success "Data directories removed"
|
||||||
else
|
else
|
||||||
log "Data kept in ${DATA_DIR} and ${CONFIG_DIR}"
|
log "Data kept in ${DATA_DIR} and ${CONFIG_DIR}"
|
||||||
fi
|
fi
|
||||||
success "Uninstall complete"
|
success "Uninstall complete"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
ensure_root
|
ensure_root
|
||||||
|
|
||||||
if [[ ${UNINSTALL} -eq 1 ]]; then
|
if [[ ${UNINSTALL} -eq 1 ]]; then
|
||||||
uninstall_all
|
uninstall_all
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_docker
|
install_docker
|
||||||
pull_image
|
pull_image
|
||||||
if [[ ${PULL_ONLY} -eq 1 ]]; then
|
if [[ ${PULL_ONLY} -eq 1 ]]; then
|
||||||
log "Pull-only requested, exiting."
|
log "Pull-only requested, exiting."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
write_env_file
|
write_env_file
|
||||||
|
|
||||||
# Always ephemeral now
|
# Always ephemeral now
|
||||||
start_container_ephemeral
|
start_container_ephemeral
|
||||||
|
|
||||||
health_check
|
health_check
|
||||||
sample_request || true
|
sample_request || true
|
||||||
|
|
||||||
# If a command is provided, run it and then shutdown container
|
# If a command is provided, run it and then shutdown container
|
||||||
if [[ ${#RUN_COMMAND[@]} -gt 0 ]]; then
|
if [[ ${#RUN_COMMAND[@]} -gt 0 ]]; then
|
||||||
log "Running user command: ${RUN_COMMAND[*]}"
|
log "Running user command: ${RUN_COMMAND[*]}"
|
||||||
set +e
|
set +e
|
||||||
"${RUN_COMMAND[@]}"
|
"${RUN_COMMAND[@]}"
|
||||||
CMD_STATUS=$?
|
CMD_STATUS=$?
|
||||||
set -e
|
set -e
|
||||||
log "Command exited with status ${CMD_STATUS}; stopping container"
|
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}
|
exit ${CMD_STATUS}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${KEEP_ALIVE} -eq 1 ]]; then
|
if [[ ${KEEP_ALIVE} -eq 1 ]]; then
|
||||||
log "Tailing logs (Ctrl-C to stop and remove container)"
|
log "Tailing logs (Ctrl-C to stop and remove container)"
|
||||||
trap 'log "Stopping container"; docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true; exit 0' INT TERM
|
trap 'log "Stopping container"; docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true; exit 0' INT TERM
|
||||||
docker logs -f "${SERVICE_NAME}"
|
docker logs -f "${SERVICE_NAME}"
|
||||||
log "Logs ended; stopping container"
|
log "Logs ended; stopping container"
|
||||||
docker stop "${SERVICE_NAME}" >/dev/null 2>&1 || true
|
docker stop "${SERVICE_NAME}" > /dev/null 2>&1 || true
|
||||||
else
|
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}"
|
log "Stop manually with: docker stop ${SERVICE_NAME}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "${BOLD}LibreTranslate is ready.${RESET}"
|
echo "${BOLD}LibreTranslate is ready.${RESET}"
|
||||||
echo "Endpoint: http://$(hostname -I | awk '{print $1}'):${PORT}"
|
echo "Endpoint: http://$(hostname -I | awk '{print $1}'):${PORT}"
|
||||||
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
if [[ ${DISABLE_API_KEY} -eq 0 ]]; then
|
||||||
if [[ ${GENERATED:-0} -eq 1 ]]; then
|
if [[ ${GENERATED:-0} -eq 1 ]]; then
|
||||||
echo "Generated API key: ${API_KEY}"
|
echo "Generated API key: ${API_KEY}"
|
||||||
else
|
else
|
||||||
echo "API key: ${API_KEY}"
|
echo "API key: ${API_KEY}"
|
||||||
fi
|
fi
|
||||||
echo "Use header: Authorization: <API_KEY>"
|
echo "Use header: Authorization: <API_KEY>"
|
||||||
else
|
else
|
||||||
echo "API key authentication DISABLED (public instance)."
|
echo "API key authentication DISABLED (public instance)."
|
||||||
fi
|
fi
|
||||||
if [[ -n ${PRELOAD_LANGS} ]]; then
|
if [[ -n ${PRELOAD_LANGS} ]]; then
|
||||||
echo "Preloaded languages requested: ${PRELOAD_LANGS}"
|
echo "Preloaded languages requested: ${PRELOAD_LANGS}"
|
||||||
fi
|
fi
|
||||||
echo "Environment file: ${ENV_FILE}"
|
echo "Environment file: ${ENV_FILE}"
|
||||||
echo "Manage: docker logs -f ${SERVICE_NAME} | docker stop ${SERVICE_NAME}"
|
echo "Manage: docker logs -f ${SERVICE_NAME} | docker stop ${SERVICE_NAME}"
|
||||||
echo "Uninstall: sudo ${SCRIPT_NAME} --uninstall"
|
echo "Uninstall: sudo ${SCRIPT_NAME} --uninstall"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -15,7 +15,7 @@ PY_HELPERS="$TOOLS_DIR/transcribe_helpers.py"
|
|||||||
VENV_DIR="$PROJECT_DIR/.venv"
|
VENV_DIR="$PROJECT_DIR/.venv"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<USAGE
|
cat << USAGE
|
||||||
Usage: $(basename "$0") [--online] [--prepare-model NAME --model-dir DIR] [-m model] [-l lang] [-o outdir] [audio_file]
|
Usage: $(basename "$0") [--online] [--prepare-model NAME --model-dir DIR] [-m model] [-l lang] [-o outdir] [audio_file]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
@ -31,443 +31,443 @@ USAGE
|
|||||||
}
|
}
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
echo "[$(date +'%H:%M:%S')]" "$@"
|
echo "[$(date +'%H:%M:%S')]" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_pkg_mgr() {
|
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
|
echo apt
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v dnf >/dev/null 2>&1; then
|
if command -v dnf > /dev/null 2>&1; then
|
||||||
echo dnf
|
echo dnf
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v yum >/dev/null 2>&1; then
|
if command -v yum > /dev/null 2>&1; then
|
||||||
echo yum
|
echo yum
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v pacman >/dev/null 2>&1; then
|
if command -v pacman > /dev/null 2>&1; then
|
||||||
echo pacman
|
echo pacman
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if command -v zypper >/dev/null 2>&1; then
|
if command -v zypper > /dev/null 2>&1; then
|
||||||
echo zypper
|
echo zypper
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo none
|
echo none
|
||||||
}
|
}
|
||||||
|
|
||||||
has_libcublas12() {
|
has_libcublas12() {
|
||||||
# Common system locations
|
# Common system locations
|
||||||
for d in \
|
for d in \
|
||||||
/usr/lib \
|
/usr/lib \
|
||||||
/usr/lib64 \
|
/usr/lib64 \
|
||||||
/usr/local/cuda/lib64 \
|
/usr/local/cuda/lib64 \
|
||||||
/usr/local/cuda-12*/lib64 \
|
/usr/local/cuda-12*/lib64 \
|
||||||
/opt/cuda/lib64 \
|
/opt/cuda/lib64 \
|
||||||
/opt/cuda/targets/x86_64-linux/lib; do
|
/opt/cuda/targets/x86_64-linux/lib; do
|
||||||
if [[ -e "$d/libcublas.so.12" ]]; then
|
if [[ -e "$d/libcublas.so.12" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
# venv-provided NVIDIA CUDA libs
|
# venv-provided NVIDIA CUDA libs
|
||||||
if [[ -x "$VENV_DIR/bin/python" ]]; then
|
if [[ -x "$VENV_DIR/bin/python" ]]; then
|
||||||
local pyver
|
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
|
if [[ -n $pyver ]]; then
|
||||||
for d in "$VENV_DIR/lib/python$pyver/site-packages/nvidia/cublas/lib" \
|
for d in "$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/cudnn/lib" \
|
||||||
"$VENV_DIR/lib/python$pyver/site-packages/nvidia/cuda_runtime/lib"; do
|
"$VENV_DIR/lib/python$pyver/site-packages/nvidia/cuda_runtime/lib"; do
|
||||||
if [[ -e "$d/libcublas.so.12" ]]; then
|
if [[ -e "$d/libcublas.so.12" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_cuda_runtime() {
|
ensure_cuda_runtime() {
|
||||||
local mgr
|
local mgr
|
||||||
mgr="$(detect_pkg_mgr)"
|
mgr="$(detect_pkg_mgr)"
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
if has_libcublas12; then return 0; fi
|
if has_libcublas12; then return 0; fi
|
||||||
echo "CUDA runtime (libcublas.so.12) not found and offline mode is enabled. Install CUDA 12 runtime or rerun with --online." >&2
|
echo "CUDA runtime (libcublas.so.12) not found and offline mode is enabled. Install CUDA 12 runtime or rerun with --online." >&2
|
||||||
exit 6
|
exit 6
|
||||||
fi
|
fi
|
||||||
if has_libcublas12; then
|
if has_libcublas12; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
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."
|
log "sudo not found; skipping CUDA runtime install attempt."
|
||||||
else
|
else
|
||||||
log "CUDA cuBLAS 12 not found; attempting to install CUDA runtime (manager: $mgr)"
|
log "CUDA cuBLAS 12 not found; attempting to install CUDA runtime (manager: $mgr)"
|
||||||
set +e
|
set +e
|
||||||
case "$mgr" in
|
case "$mgr" in
|
||||||
pacman)
|
pacman)
|
||||||
sudo pacman -Sy --noconfirm cuda cudnn || true
|
sudo pacman -Sy --noconfirm cuda cudnn || true
|
||||||
;;
|
;;
|
||||||
apt)
|
apt)
|
||||||
sudo apt-get update -y || true
|
sudo apt-get update -y || true
|
||||||
sudo apt-get install -y nvidia-cuda-toolkit || true
|
sudo apt-get install -y nvidia-cuda-toolkit || true
|
||||||
;;
|
;;
|
||||||
dnf | yum)
|
dnf | yum)
|
||||||
sudo "$mgr" install -y cuda cudnn || true
|
sudo "$mgr" install -y cuda cudnn || true
|
||||||
;;
|
;;
|
||||||
zypper)
|
zypper)
|
||||||
sudo zypper install -y cuda cudnn || true
|
sudo zypper install -y cuda cudnn || true
|
||||||
;;
|
;;
|
||||||
*) log "Unknown package manager; cannot install CUDA automatically." ;;
|
*) log "Unknown package manager; cannot install CUDA automatically." ;;
|
||||||
esac
|
esac
|
||||||
set -e
|
set -e
|
||||||
fi
|
fi
|
||||||
# Re-check
|
# Re-check
|
||||||
if ! has_libcublas12; then
|
if ! has_libcublas12; then
|
||||||
echo "CUDA runtime (libcublas.so.12) not found after attempted install. Please install CUDA 12 toolkit/runtime and re-run." >&2
|
echo "CUDA runtime (libcublas.so.12) not found after attempted install. Please install CUDA 12 toolkit/runtime and re-run." >&2
|
||||||
exit 6
|
exit 6
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_system_deps() {
|
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
|
local need_ffmpeg=0 need_espeak=0
|
||||||
have_cmd ffmpeg || need_ffmpeg=1
|
have_cmd ffmpeg || need_ffmpeg=1
|
||||||
have_cmd espeak-ng || need_espeak=1
|
have_cmd espeak-ng || need_espeak=1
|
||||||
|
|
||||||
# If diarization requested and online, we may also try to ensure libsndfile
|
# If diarization requested and online, we may also try to ensure libsndfile
|
||||||
local need_libsndfile=0
|
local need_libsndfile=0
|
||||||
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
||||||
# Heuristic: check common library file
|
# Heuristic: check common library file
|
||||||
if [[ ! -e /usr/lib/x86_64-linux-gnu/libsndfile.so && ! -e /usr/lib/libsndfile.so && ! -e /usr/lib64/libsndfile.so ]]; then
|
if [[ ! -e /usr/lib/x86_64-linux-gnu/libsndfile.so && ! -e /usr/lib/libsndfile.so && ! -e /usr/lib64/libsndfile.so ]]; then
|
||||||
need_libsndfile=1
|
need_libsndfile=1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $need_ffmpeg -eq 0 && $need_espeak -eq 0 && $need_libsndfile -eq 0 ]]; then
|
if [[ $need_ffmpeg -eq 0 && $need_espeak -eq 0 && $need_libsndfile -eq 0 ]]; then
|
||||||
log "System deps present: ffmpeg, espeak-ng${FW_DIARIZE:+, libsndfile}"
|
log "System deps present: ffmpeg, espeak-ng${FW_DIARIZE:+, libsndfile}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
echo "Missing system dependencies (ffmpeg/espeak-ng) but running in offline mode. Install them or rerun with --online." >&2
|
echo "Missing system dependencies (ffmpeg/espeak-ng) but running in offline mode. Install them or rerun with --online." >&2
|
||||||
exit 5
|
exit 5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local mgr
|
local mgr
|
||||||
mgr="$(detect_pkg_mgr)"
|
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))"
|
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."
|
log "sudo not found; skipping system package installation attempt."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Avoid exiting on install errors; continue best-effort
|
# Avoid exiting on install errors; continue best-effort
|
||||||
set +e
|
set +e
|
||||||
case "$mgr" in
|
case "$mgr" in
|
||||||
apt)
|
apt)
|
||||||
sudo apt-get update -y || log "apt-get update failed; continuing"
|
sudo apt-get update -y || log "apt-get update failed; continuing"
|
||||||
pkgs=(python3-venv python3-pip)
|
pkgs=(python3-venv python3-pip)
|
||||||
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
||||||
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
||||||
if [[ $need_libsndfile -eq 1 ]]; then
|
if [[ $need_libsndfile -eq 1 ]]; then
|
||||||
# Try both names across releases
|
# Try both names across releases
|
||||||
pkgs+=(libsndfile1)
|
pkgs+=(libsndfile1)
|
||||||
sudo apt-get install -y libsndfile1 || true
|
sudo apt-get install -y libsndfile1 || true
|
||||||
# If that failed, try libsndfile2 (newer distros)
|
# If that failed, try libsndfile2 (newer distros)
|
||||||
sudo apt-get install -y libsndfile2 || true
|
sudo apt-get install -y libsndfile2 || true
|
||||||
fi
|
fi
|
||||||
sudo apt-get install -y "${pkgs[@]}" || log "apt-get install failed; continuing"
|
sudo apt-get install -y "${pkgs[@]}" || log "apt-get install failed; continuing"
|
||||||
;;
|
;;
|
||||||
dnf)
|
dnf)
|
||||||
pkgs=(python3-venv python3-pip)
|
pkgs=(python3-venv python3-pip)
|
||||||
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
||||||
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
||||||
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
||||||
sudo dnf install -y "${pkgs[@]}" || log "dnf install failed; continuing"
|
sudo dnf install -y "${pkgs[@]}" || log "dnf install failed; continuing"
|
||||||
;;
|
;;
|
||||||
yum)
|
yum)
|
||||||
pkgs=(python3-venv python3-pip)
|
pkgs=(python3-venv python3-pip)
|
||||||
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
||||||
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
||||||
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
||||||
sudo yum install -y "${pkgs[@]}" || log "yum install failed; continuing"
|
sudo yum install -y "${pkgs[@]}" || log "yum install failed; continuing"
|
||||||
;;
|
;;
|
||||||
pacman)
|
pacman)
|
||||||
pkgs=(python-virtualenv python-pip)
|
pkgs=(python-virtualenv python-pip)
|
||||||
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
||||||
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
||||||
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile)
|
||||||
sudo pacman -Sy --noconfirm "${pkgs[@]}" || log "pacman install failed; continuing"
|
sudo pacman -Sy --noconfirm "${pkgs[@]}" || log "pacman install failed; continuing"
|
||||||
;;
|
;;
|
||||||
zypper)
|
zypper)
|
||||||
pkgs=(python311-virtualenv python311-pip)
|
pkgs=(python311-virtualenv python311-pip)
|
||||||
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
[[ $need_ffmpeg -eq 1 ]] && pkgs+=(ffmpeg)
|
||||||
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
[[ $need_espeak -eq 1 ]] && pkgs+=(espeak-ng)
|
||||||
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile1)
|
[[ $need_libsndfile -eq 1 ]] && pkgs+=(libsndfile1)
|
||||||
sudo zypper install -y "${pkgs[@]}" || log "zypper install failed; continuing"
|
sudo zypper install -y "${pkgs[@]}" || log "zypper install failed; continuing"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
log "Unknown package manager; please ensure ffmpeg and espeak-ng are installed."
|
log "Unknown package manager; please ensure ffmpeg and espeak-ng are installed."
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
set -e
|
set -e
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_venv() {
|
setup_venv() {
|
||||||
if [[ ! -d $VENV_DIR ]]; then
|
if [[ ! -d $VENV_DIR ]]; then
|
||||||
log "Creating venv at $VENV_DIR"
|
log "Creating venv at $VENV_DIR"
|
||||||
python3 -m venv "$VENV_DIR"
|
python3 -m venv "$VENV_DIR"
|
||||||
fi
|
fi
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
if [[ $OFFLINE -eq 0 ]]; then
|
if [[ $OFFLINE -eq 0 ]]; then
|
||||||
python -m pip install --upgrade pip wheel setuptools
|
python -m pip install --upgrade pip wheel setuptools
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_python_deps() {
|
install_python_deps() {
|
||||||
# Install deps; if NVIDIA GPU is present, prefer CUDA-capable stack (cu12)
|
# Install deps; if NVIDIA GPU is present, prefer CUDA-capable stack (cu12)
|
||||||
local has_nvidia_flag="${1:-0}"
|
local has_nvidia_flag="${1:-0}"
|
||||||
log "Installing faster-whisper and dependencies"
|
log "Installing faster-whisper and dependencies"
|
||||||
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
export PIP_DEFAULT_TIMEOUT=${PIP_DEFAULT_TIMEOUT:-20}
|
export PIP_DEFAULT_TIMEOUT=${PIP_DEFAULT_TIMEOUT:-20}
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
# Offline: do not install, just verify modules
|
# Offline: do not install, just verify modules
|
||||||
if ! python "$PY_HELPERS" check-faster-whisper; then
|
if ! python "$PY_HELPERS" check-faster-whisper; then
|
||||||
exit 7
|
exit 7
|
||||||
fi
|
fi
|
||||||
# If diarization requested offline, check for its deps too (warn-only)
|
# If diarization requested offline, check for its deps too (warn-only)
|
||||||
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
||||||
python "$PY_HELPERS" check-diarization || true
|
python "$PY_HELPERS" check-diarization || true
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if [[ $has_nvidia_flag -eq 1 ]]; then
|
if [[ $has_nvidia_flag -eq 1 ]]; then
|
||||||
# If ctranslate2 is not installed, attempt CUDA-enabled wheel (with fallback)
|
# 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)"
|
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 ||
|
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"
|
log "Warning: could not reach cu12 wheel index; will proceed with available ctranslate2"
|
||||||
fi
|
fi
|
||||||
# Ensure NVIDIA CUDA 12 runtime libs are available inside the venv
|
# Ensure NVIDIA CUDA 12 runtime libs are available inside the venv
|
||||||
python -m pip install --progress-bar on --retries 1 --upgrade nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 ||
|
python -m pip install --progress-bar on --retries 1 --upgrade nvidia-cublas-cu12 nvidia-cuda-runtime-cu12 nvidia-cudnn-cu12 ||
|
||||||
log "Warning: failed to install NVIDIA cu12 runtime libs via pip"
|
log "Warning: failed to install NVIDIA cu12 runtime libs via pip"
|
||||||
fi
|
fi
|
||||||
python -m pip install --progress-bar on --retries 1 --upgrade faster-whisper ffmpeg-python
|
python -m pip install --progress-bar on --retries 1 --upgrade faster-whisper ffmpeg-python
|
||||||
|
|
||||||
# If diarization requested and online, install its Python deps best-effort
|
# If diarization requested and online, install its Python deps best-effort
|
||||||
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
||||||
python -m pip install --progress-bar on --retries 1 --upgrade soundfile speechbrain ||
|
python -m pip install --progress-bar on --retries 1 --upgrade soundfile speechbrain ||
|
||||||
log "Warning: failed to install soundfile/speechbrain"
|
log "Warning: failed to install soundfile/speechbrain"
|
||||||
# Torch and torchaudio CPU wheels (force to avoid mismatched CUDA builds)
|
# Torch and torchaudio CPU wheels (force to avoid mismatched CUDA builds)
|
||||||
python -m pip install --progress-bar on --retries 1 --upgrade --force-reinstall --index-url https://download.pytorch.org/whl/cpu torch torchaudio ||
|
python -m pip install --progress-bar on --retries 1 --upgrade --force-reinstall --index-url https://download.pytorch.org/whl/cpu torch torchaudio ||
|
||||||
log "Warning: failed to install torch/torchaudio CPU wheels"
|
log "Warning: failed to install torch/torchaudio CPU wheels"
|
||||||
fi
|
fi
|
||||||
python "$PY_HELPERS" deps-installed
|
python "$PY_HELPERS" deps-installed
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_runner() {
|
ensure_runner() {
|
||||||
if [[ ! -f $PY_RUNNER ]]; then
|
if [[ ! -f $PY_RUNNER ]]; then
|
||||||
echo "Runner not found: $PY_RUNNER" >&2
|
echo "Runner not found: $PY_RUNNER" >&2
|
||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
generate_test_audio() {
|
generate_test_audio() {
|
||||||
local tmpwav
|
local tmpwav
|
||||||
tmpwav="${PROJECT_DIR}/test_fw.wav"
|
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
|
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
|
fi
|
||||||
# If espeak-ng failed or not present, try espeak
|
# 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
|
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
|
fi
|
||||||
# Fallback: generate tone via Python stdlib (no external deps)
|
# Fallback: generate tone via Python stdlib (no external deps)
|
||||||
if [[ ! -s $tmpwav ]]; then
|
if [[ ! -s $tmpwav ]]; then
|
||||||
log "Generating 3s 1kHz WAV via Python stdlib -> $tmpwav" >&2
|
log "Generating 3s 1kHz WAV via Python stdlib -> $tmpwav" >&2
|
||||||
python3 "$PY_HELPERS" generate-wav --file "$tmpwav" || true
|
python3 "$PY_HELPERS" generate-wav --file "$tmpwav" || true
|
||||||
fi
|
fi
|
||||||
# Final fallback: tone via ffmpeg
|
# Final fallback: tone via ffmpeg
|
||||||
if [[ ! -s $tmpwav ]]; then
|
if [[ ! -s $tmpwav ]]; then
|
||||||
log "Creating a 3s sine tone WAV via ffmpeg -> $tmpwav" >&2
|
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
|
fi
|
||||||
echo "$tmpwav"
|
echo "$tmpwav"
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare_model() {
|
prepare_model() {
|
||||||
# Download a model for offline use into MODEL_DIR
|
# Download a model for offline use into MODEL_DIR
|
||||||
local name="$1"
|
local name="$1"
|
||||||
mkdir -p "$MODEL_DIR"
|
mkdir -p "$MODEL_DIR"
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
log "Preparing model '$name' into $MODEL_DIR"
|
log "Preparing model '$name' into $MODEL_DIR"
|
||||||
python "$PY_HELPERS" prepare-model --model "$name" --model-dir "$MODEL_DIR"
|
python "$PY_HELPERS" prepare-model --model "$name" --model-dir "$MODEL_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
# Defaults
|
# Defaults
|
||||||
OFFLINE=1
|
OFFLINE=1
|
||||||
PREPARE_MODEL=""
|
PREPARE_MODEL=""
|
||||||
MODEL_DIR="$PROJECT_DIR/models"
|
MODEL_DIR="$PROJECT_DIR/models"
|
||||||
MODEL="large-v3"
|
MODEL="large-v3"
|
||||||
LANGUAGE=""
|
LANGUAGE=""
|
||||||
OUTDIR=""
|
OUTDIR=""
|
||||||
INPUT_FILE=""
|
INPUT_FILE=""
|
||||||
|
|
||||||
# Parse args
|
# Parse args
|
||||||
PARSED=$(getopt -o m:l:o:h -l online,prepare-model:,model-dir: -- "$@") || {
|
PARSED=$(getopt -o m:l:o:h -l online,prepare-model:,model-dir: -- "$@") || {
|
||||||
usage
|
usage
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
eval set -- "$PARSED"
|
eval set -- "$PARSED"
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-m)
|
-m)
|
||||||
MODEL="$2"
|
MODEL="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-l)
|
-l)
|
||||||
LANGUAGE="$2"
|
LANGUAGE="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-o)
|
-o)
|
||||||
OUTDIR="$2"
|
OUTDIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-h)
|
-h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
--online)
|
--online)
|
||||||
OFFLINE=0
|
OFFLINE=0
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
--prepare-model)
|
--prepare-model)
|
||||||
PREPARE_MODEL="$2"
|
PREPARE_MODEL="$2"
|
||||||
OFFLINE=0
|
OFFLINE=0
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--model-dir)
|
--model-dir)
|
||||||
MODEL_DIR="$2"
|
MODEL_DIR="$2"
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--)
|
--)
|
||||||
shift
|
shift
|
||||||
break
|
break
|
||||||
;;
|
;;
|
||||||
*) break ;;
|
*) break ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
INPUT_FILE="${1:-}"
|
INPUT_FILE="${1:-}"
|
||||||
|
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
export HF_HUB_OFFLINE=1
|
export HF_HUB_OFFLINE=1
|
||||||
export TRANSFORMERS_OFFLINE=1
|
export TRANSFORMERS_OFFLINE=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_system_deps
|
install_system_deps
|
||||||
setup_venv
|
setup_venv
|
||||||
|
|
||||||
# If asked to prepare a model, do that and exit
|
# If asked to prepare a model, do that and exit
|
||||||
if [[ -n $PREPARE_MODEL ]]; then
|
if [[ -n $PREPARE_MODEL ]]; then
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
echo "--prepare-model requires network; rerun with --online." >&2
|
echo "--prepare-model requires network; rerun with --online." >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
install_python_deps 0
|
install_python_deps 0
|
||||||
prepare_model "$PREPARE_MODEL"
|
prepare_model "$PREPARE_MODEL"
|
||||||
log "Model '$PREPARE_MODEL' downloaded to $MODEL_DIR"
|
log "Model '$PREPARE_MODEL' downloaded to $MODEL_DIR"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Detect NVIDIA GPU and enforce CUDA if present
|
# Detect NVIDIA GPU and enforce CUDA if present
|
||||||
has_nvidia=0
|
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
|
has_nvidia=1
|
||||||
fi
|
fi
|
||||||
install_python_deps "$has_nvidia"
|
install_python_deps "$has_nvidia"
|
||||||
ensure_runner
|
ensure_runner
|
||||||
|
|
||||||
local input="$INPUT_FILE"
|
local input="$INPUT_FILE"
|
||||||
if [[ -z $input ]]; then
|
if [[ -z $input ]]; then
|
||||||
input="$(generate_test_audio)"
|
input="$(generate_test_audio)"
|
||||||
if [[ ! -s $input ]]; then
|
if [[ ! -s $input ]]; then
|
||||||
echo "Failed to generate test audio. Please provide an audio file." >&2
|
echo "Failed to generate test audio. Please provide an audio file." >&2
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $input ]]; then
|
if [[ ! -f $input ]]; then
|
||||||
echo "Input file not found: $input" >&2
|
echo "Input file not found: $input" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local args=("$input" "--model" "$MODEL")
|
local args=("$input" "--model" "$MODEL")
|
||||||
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
|
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
|
||||||
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
|
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
|
||||||
|
|
||||||
# Pass diarization via env if requested
|
# Pass diarization via env if requested
|
||||||
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
if [[ ${FW_DIARIZE:-} == "1" ]]; then
|
||||||
args+=("--diarize")
|
args+=("--diarize")
|
||||||
if [[ -n ${FW_NUM_SPEAKERS:-} ]]; then
|
if [[ -n ${FW_NUM_SPEAKERS:-} ]]; then
|
||||||
args+=("--num-speakers" "${FW_NUM_SPEAKERS}")
|
args+=("--num-speakers" "${FW_NUM_SPEAKERS}")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $has_nvidia -eq 1 ]]; then
|
if [[ $has_nvidia -eq 1 ]]; then
|
||||||
ensure_cuda_runtime
|
ensure_cuda_runtime
|
||||||
# Export common CUDA paths in case the env lacks them
|
# Export common CUDA paths in case the env lacks them
|
||||||
export CUDA_HOME="${CUDA_HOME:-/usr/local/cuda}"
|
export CUDA_HOME="${CUDA_HOME:-/usr/local/cuda}"
|
||||||
# Include system and possible venv-provided CUDA libs
|
# Include system and possible venv-provided CUDA libs
|
||||||
local pyver venv_cuda_paths=""
|
local pyver venv_cuda_paths=""
|
||||||
if [[ -x "$VENV_DIR/bin/python" ]]; then
|
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
|
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"
|
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
|
fi
|
||||||
fi
|
fi
|
||||||
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:${CUDA_HOME}/lib64:/usr/lib/x86_64-linux-gnu:/opt/cuda/lib64:/opt/cuda/targets/x86_64-linux/lib:${venv_cuda_paths}"
|
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:${CUDA_HOME}/lib64:/usr/lib/x86_64-linux-gnu:/opt/cuda/lib64:/opt/cuda/targets/x86_64-linux/lib:${venv_cuda_paths}"
|
||||||
export PATH="${PATH}:${CUDA_HOME}/bin"
|
export PATH="${PATH}:${CUDA_HOME}/bin"
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
python "$PY_HELPERS" test-cuda || {
|
python "$PY_HELPERS" test-cuda || {
|
||||||
echo "CUDA environment check failed. Aborting as requested." >&2
|
echo "CUDA environment check failed. Aborting as requested." >&2
|
||||||
exit 6
|
exit 6
|
||||||
}
|
}
|
||||||
args+=("--device" "cuda")
|
args+=("--device" "cuda")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Transcribing: $input"
|
log "Transcribing: $input"
|
||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
if [[ $has_nvidia -eq 1 ]]; then
|
if [[ $has_nvidia -eq 1 ]]; then
|
||||||
if ! python "$PY_RUNNER" "${args[@]}"; then
|
if ! python "$PY_RUNNER" "${args[@]}"; then
|
||||||
echo "CUDA execution requested due to detected NVIDIA GPU, but it failed. Aborting as requested (no CPU fallback)." >&2
|
echo "CUDA execution requested due to detected NVIDIA GPU, but it failed. Aborting as requested (no CPU fallback)." >&2
|
||||||
exit 6
|
exit 6
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Offline: prefer local directory if present; otherwise use cache without network
|
# Offline: prefer local directory if present; otherwise use cache without network
|
||||||
if [[ $OFFLINE -eq 1 ]]; then
|
if [[ $OFFLINE -eq 1 ]]; then
|
||||||
local local_model_path=""
|
local local_model_path=""
|
||||||
if [[ -d $MODEL ]]; then
|
if [[ -d $MODEL ]]; then
|
||||||
local_model_path="$MODEL"
|
local_model_path="$MODEL"
|
||||||
elif [[ -d "$MODEL_DIR/$MODEL" ]]; then
|
elif [[ -d "$MODEL_DIR/$MODEL" ]]; then
|
||||||
local_model_path="$MODEL_DIR/$MODEL"
|
local_model_path="$MODEL_DIR/$MODEL"
|
||||||
fi
|
fi
|
||||||
if [[ -n $local_model_path ]]; then
|
if [[ -n $local_model_path ]]; then
|
||||||
args=("$input" "--model" "$local_model_path")
|
args=("$input" "--model" "$local_model_path")
|
||||||
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
|
[[ -n $LANGUAGE ]] && args+=("--language" "$LANGUAGE")
|
||||||
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
|
[[ -n $OUTDIR ]] && args+=("--outdir" "$OUTDIR")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
python "$PY_RUNNER" "${args[@]}"
|
python "$PY_RUNNER" "${args[@]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -45,231 +45,231 @@ TEMPLATE_LOGROTATE="$LOGROTATE_TEMPLATES/periodic-system-maintenance"
|
|||||||
|
|
||||||
# Function to verify required files exist
|
# Function to verify required files exist
|
||||||
verify_files() {
|
verify_files() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Verifying Required Files..."
|
echo "1. Verifying Required Files..."
|
||||||
echo "=============================="
|
echo "=============================="
|
||||||
|
|
||||||
local missing_files=()
|
local missing_files=()
|
||||||
|
|
||||||
if [[ ! -f $PACMAN_WRAPPER_SCRIPT ]]; then
|
if [[ ! -f $PACMAN_WRAPPER_SCRIPT ]]; then
|
||||||
missing_files+=("$PACMAN_WRAPPER_SCRIPT")
|
missing_files+=("$PACMAN_WRAPPER_SCRIPT")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $PACMAN_WRAPPER_INSTALL ]]; then
|
if [[ ! -f $PACMAN_WRAPPER_INSTALL ]]; then
|
||||||
missing_files+=("$PACMAN_WRAPPER_INSTALL")
|
missing_files+=("$PACMAN_WRAPPER_INSTALL")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f $HOSTS_INSTALL_SCRIPT ]]; then
|
if [[ ! -f $HOSTS_INSTALL_SCRIPT ]]; then
|
||||||
missing_files+=("$HOSTS_INSTALL_SCRIPT")
|
missing_files+=("$HOSTS_INSTALL_SCRIPT")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check template files as well
|
# Check template files as well
|
||||||
for tmpl in \
|
for tmpl in \
|
||||||
"$TEMPLATE_MAINT_SCRIPT" \
|
"$TEMPLATE_MAINT_SCRIPT" \
|
||||||
"$TEMPLATE_HOSTS_MONITOR" \
|
"$TEMPLATE_HOSTS_MONITOR" \
|
||||||
"$TEMPLATE_BROWSER_WRAPPER" \
|
"$TEMPLATE_BROWSER_WRAPPER" \
|
||||||
"$TEMPLATE_SVC_MAINT" \
|
"$TEMPLATE_SVC_MAINT" \
|
||||||
"$TEMPLATE_TIMER" \
|
"$TEMPLATE_TIMER" \
|
||||||
"$TEMPLATE_STARTUP" \
|
"$TEMPLATE_STARTUP" \
|
||||||
"$TEMPLATE_HOSTS_SVC" \
|
"$TEMPLATE_HOSTS_SVC" \
|
||||||
"$TEMPLATE_LOGROTATE"; do
|
"$TEMPLATE_LOGROTATE"; do
|
||||||
if [[ ! -f $tmpl ]]; then
|
if [[ ! -f $tmpl ]]; then
|
||||||
missing_files+=("$tmpl")
|
missing_files+=("$tmpl")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
if [[ ${#missing_files[@]} -gt 0 ]]; then
|
||||||
echo "Error: The following required files are missing:"
|
echo "Error: The following required files are missing:"
|
||||||
for file in "${missing_files[@]}"; do
|
for file in "${missing_files[@]}"; do
|
||||||
echo " - $file"
|
echo " - $file"
|
||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ All required files found"
|
echo "✓ All required files found"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the combined execution script
|
# Function to create the combined execution script
|
||||||
create_execution_script() {
|
create_execution_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Creating Combined Execution Script..."
|
echo "2. Creating Combined Execution Script..."
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
|
|
||||||
local exec_script="/usr/local/bin/periodic-system-maintenance.sh"
|
local exec_script="/usr/local/bin/periodic-system-maintenance.sh"
|
||||||
|
|
||||||
# Install from template with path substitutions
|
# Install from template with path substitutions
|
||||||
sed \
|
sed \
|
||||||
-e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \
|
-e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \
|
||||||
-e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
-e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
||||||
"$TEMPLATE_MAINT_SCRIPT" >"$exec_script"
|
"$TEMPLATE_MAINT_SCRIPT" > "$exec_script"
|
||||||
|
|
||||||
chmod +x "$exec_script"
|
chmod +x "$exec_script"
|
||||||
echo "✓ Installed execution script from template: $exec_script"
|
echo "✓ Installed execution script from template: $exec_script"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create systemd service
|
# Function to create systemd service
|
||||||
create_systemd_service() {
|
create_systemd_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Creating Systemd Service..."
|
echo "3. Creating Systemd Service..."
|
||||||
echo "============================="
|
echo "============================="
|
||||||
|
|
||||||
local service_file="/etc/systemd/system/periodic-system-maintenance.service"
|
local service_file="/etc/systemd/system/periodic-system-maintenance.service"
|
||||||
install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file"
|
install -m 0644 "$TEMPLATE_SVC_MAINT" "$service_file"
|
||||||
echo "✓ Installed systemd service from template: $service_file"
|
echo "✓ Installed systemd service from template: $service_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create systemd timer for hourly execution
|
# Function to create systemd timer for hourly execution
|
||||||
create_systemd_timer() {
|
create_systemd_timer() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Creating Systemd Timer..."
|
echo "4. Creating Systemd Timer..."
|
||||||
echo "============================"
|
echo "============================"
|
||||||
|
|
||||||
local timer_file="/etc/systemd/system/periodic-system-maintenance.timer"
|
local timer_file="/etc/systemd/system/periodic-system-maintenance.timer"
|
||||||
install -m 0644 "$TEMPLATE_TIMER" "$timer_file"
|
install -m 0644 "$TEMPLATE_TIMER" "$timer_file"
|
||||||
echo "✓ Installed systemd timer from template: $timer_file"
|
echo "✓ Installed systemd timer from template: $timer_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create startup service (additional to timer)
|
# Function to create startup service (additional to timer)
|
||||||
create_startup_service() {
|
create_startup_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "5. Creating Startup Service..."
|
echo "5. Creating Startup Service..."
|
||||||
echo "=============================="
|
echo "=============================="
|
||||||
|
|
||||||
local startup_service="/etc/systemd/system/periodic-system-startup.service"
|
local startup_service="/etc/systemd/system/periodic-system-startup.service"
|
||||||
install -m 0644 "$TEMPLATE_STARTUP" "$startup_service"
|
install -m 0644 "$TEMPLATE_STARTUP" "$startup_service"
|
||||||
echo "✓ Installed startup service from template: $startup_service"
|
echo "✓ Installed startup service from template: $startup_service"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create hosts file monitor service
|
# Function to create hosts file monitor service
|
||||||
create_hosts_monitor_service() {
|
create_hosts_monitor_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Creating Hosts File Monitor Service..."
|
echo "6. Creating Hosts File Monitor Service..."
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
local monitor_script="/usr/local/bin/hosts-file-monitor.sh"
|
local monitor_script="/usr/local/bin/hosts-file-monitor.sh"
|
||||||
local monitor_service="/etc/systemd/system/hosts-file-monitor.service"
|
local monitor_service="/etc/systemd/system/hosts-file-monitor.service"
|
||||||
|
|
||||||
# Install the monitor script from template with substitution
|
# Install the monitor script from template with substitution
|
||||||
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
||||||
"$TEMPLATE_HOSTS_MONITOR" >"$monitor_script"
|
"$TEMPLATE_HOSTS_MONITOR" > "$monitor_script"
|
||||||
chmod +x "$monitor_script"
|
chmod +x "$monitor_script"
|
||||||
echo "✓ Installed hosts monitor script from template: $monitor_script"
|
echo "✓ Installed hosts monitor script from template: $monitor_script"
|
||||||
|
|
||||||
# Install the systemd service from template
|
# Install the systemd service from template
|
||||||
install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service"
|
install -m 0644 "$TEMPLATE_HOSTS_SVC" "$monitor_service"
|
||||||
echo "✓ Installed hosts monitor service from template: $monitor_service"
|
echo "✓ Installed hosts monitor service from template: $monitor_service"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to install browser pre-exec wrapper and wire common browser names
|
# Function to install browser pre-exec wrapper and wire common browser names
|
||||||
install_browser_preexec_wrapper() {
|
install_browser_preexec_wrapper() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6.1 Installing Browser Pre-Exec Wrapper..."
|
echo "6.1 Installing Browser Pre-Exec Wrapper..."
|
||||||
echo "========================================="
|
echo "========================================="
|
||||||
|
|
||||||
local wrapper="/usr/local/bin/browser-preexec-wrapper"
|
local wrapper="/usr/local/bin/browser-preexec-wrapper"
|
||||||
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
|
||||||
"$TEMPLATE_BROWSER_WRAPPER" >"$wrapper"
|
"$TEMPLATE_BROWSER_WRAPPER" > "$wrapper"
|
||||||
chmod +x "$wrapper"
|
chmod +x "$wrapper"
|
||||||
echo "✓ Installed wrapper: $wrapper"
|
echo "✓ Installed wrapper: $wrapper"
|
||||||
|
|
||||||
# Allow passwordless execution of hosts installer for root-only actions
|
# Allow passwordless execution of hosts installer for root-only actions
|
||||||
local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd"
|
local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd"
|
||||||
if command -v visudo >/dev/null 2>&1; then
|
if command -v visudo > /dev/null 2>&1; then
|
||||||
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" >"$sudoers_file"
|
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file"
|
||||||
chmod 440 "$sudoers_file"
|
chmod 440 "$sudoers_file"
|
||||||
# Validate syntax
|
# 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"
|
echo "✓ Sudoers drop-in created: $sudoers_file"
|
||||||
else
|
else
|
||||||
echo "visudo not found; skipping sudoers drop-in"
|
echo "visudo not found; skipping sudoers drop-in"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create symlinks for common browser commands to the wrapper in /usr/local/bin
|
# Create symlinks for common browser commands to the wrapper in /usr/local/bin
|
||||||
# This takes precedence over /usr/bin in PATH on most systems.
|
# This takes precedence over /usr/bin in PATH on most systems.
|
||||||
local browsers=("thorium-browser" "google-chrome" "google-chrome-stable" "chromium" "brave" "brave-browser" "vivaldi-stable" "firefox")
|
local browsers=("thorium-browser" "google-chrome" "google-chrome-stable" "chromium" "brave" "brave-browser" "vivaldi-stable" "firefox")
|
||||||
for b in "${browsers[@]}"; do
|
for b in "${browsers[@]}"; do
|
||||||
local link="/usr/local/bin/$b"
|
local link="/usr/local/bin/$b"
|
||||||
ln -sf "$wrapper" "$link"
|
ln -sf "$wrapper" "$link"
|
||||||
done
|
done
|
||||||
echo "✓ Symlinked wrapper for common browsers in /usr/local/bin"
|
echo "✓ Symlinked wrapper for common browsers in /usr/local/bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to enable and start services
|
# Function to enable and start services
|
||||||
enable_services() {
|
enable_services() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "7. Enabling Services and Timer..."
|
echo "7. Enabling Services and Timer..."
|
||||||
echo "================================="
|
echo "================================="
|
||||||
|
|
||||||
# Reload systemd daemon
|
# Reload systemd daemon
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
echo "✓ Systemd daemon reloaded"
|
echo "✓ Systemd daemon reloaded"
|
||||||
|
|
||||||
# Enable and start the timer
|
# Enable and start the timer
|
||||||
systemctl enable periodic-system-maintenance.timer
|
systemctl enable periodic-system-maintenance.timer
|
||||||
systemctl start periodic-system-maintenance.timer
|
systemctl start periodic-system-maintenance.timer
|
||||||
echo "✓ Timer enabled and started"
|
echo "✓ Timer enabled and started"
|
||||||
|
|
||||||
# Enable startup service (but don't start it now)
|
# Enable startup service (but don't start it now)
|
||||||
systemctl enable periodic-system-startup.service
|
systemctl enable periodic-system-startup.service
|
||||||
echo "✓ Startup service enabled"
|
echo "✓ Startup service enabled"
|
||||||
|
|
||||||
# Enable hosts file monitor service
|
# Enable hosts file monitor service
|
||||||
systemctl enable hosts-file-monitor.service
|
systemctl enable hosts-file-monitor.service
|
||||||
systemctl start hosts-file-monitor.service
|
systemctl start hosts-file-monitor.service
|
||||||
echo "✓ Hosts file monitor service enabled and started"
|
echo "✓ Hosts file monitor service enabled and started"
|
||||||
|
|
||||||
# Show timer status
|
# Show timer status
|
||||||
echo ""
|
echo ""
|
||||||
echo "Timer Status:"
|
echo "Timer Status:"
|
||||||
systemctl status periodic-system-maintenance.timer --no-pager -l
|
systemctl status periodic-system-maintenance.timer --no-pager -l
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Hosts Monitor Status:"
|
echo "Hosts Monitor Status:"
|
||||||
systemctl status hosts-file-monitor.service --no-pager -l
|
systemctl status hosts-file-monitor.service --no-pager -l
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next scheduled runs:"
|
echo "Next scheduled runs:"
|
||||||
systemctl list-timers periodic-system-maintenance.timer --no-pager
|
systemctl list-timers periodic-system-maintenance.timer --no-pager
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create log rotation configuration
|
# Function to create log rotation configuration
|
||||||
create_log_rotation() {
|
create_log_rotation() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "8. Setting up Log Rotation..."
|
echo "8. Setting up Log Rotation..."
|
||||||
echo "============================="
|
echo "============================="
|
||||||
|
|
||||||
local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance"
|
local logrotate_conf="/etc/logrotate.d/periodic-system-maintenance"
|
||||||
install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf"
|
install -m 0644 "$TEMPLATE_LOGROTATE" "$logrotate_conf"
|
||||||
echo "✓ Installed log rotation configuration from template: $logrotate_conf"
|
echo "✓ Installed log rotation configuration from template: $logrotate_conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to run initial execution
|
# Function to run initial execution
|
||||||
run_initial_execution() {
|
run_initial_execution() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "9. Running Initial Execution..."
|
echo "9. Running Initial Execution..."
|
||||||
echo "==============================="
|
echo "==============================="
|
||||||
|
|
||||||
local run_initial=true
|
local run_initial=true
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
echo "Would you like to run the system maintenance now to test the setup?"
|
echo "Would you like to run the system maintenance now to test the setup?"
|
||||||
read -p "Run initial execution? (y/N): " -n 1 -r
|
read -p "Run initial execution? (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
run_initial=false
|
run_initial=false
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Auto-running initial execution to test the setup (use --interactive to prompt)"
|
echo "Auto-running initial execution to test the setup (use --interactive to prompt)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $run_initial == "true" ]]; then
|
if [[ $run_initial == "true" ]]; then
|
||||||
echo "Running initial system maintenance..."
|
echo "Running initial system maintenance..."
|
||||||
/usr/local/bin/periodic-system-maintenance.sh
|
/usr/local/bin/periodic-system-maintenance.sh
|
||||||
echo "✓ Initial execution completed"
|
echo "✓ Initial execution completed"
|
||||||
else
|
else
|
||||||
echo "Skipping initial execution"
|
echo "Skipping initial execution"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
|
|||||||
@ -24,72 +24,72 @@ echo "User home: $USER_HOME"
|
|||||||
|
|
||||||
# Function to check if Thorium browser is installed
|
# Function to check if Thorium browser is installed
|
||||||
check_thorium_browser() {
|
check_thorium_browser() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Checking Thorium Browser Installation..."
|
echo "1. Checking Thorium Browser Installation..."
|
||||||
echo "=========================================="
|
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 "Warning: Thorium browser not found in PATH"
|
||||||
echo "Checking alternative locations..."
|
echo "Checking alternative locations..."
|
||||||
|
|
||||||
# Check common installation paths
|
# Check common installation paths
|
||||||
local alt_paths=(
|
local alt_paths=(
|
||||||
"/opt/thorium/thorium"
|
"/opt/thorium/thorium"
|
||||||
"/usr/bin/thorium"
|
"/usr/bin/thorium"
|
||||||
"/usr/local/bin/thorium"
|
"/usr/local/bin/thorium"
|
||||||
"/opt/thorium-browser/thorium-browser"
|
"/opt/thorium-browser/thorium-browser"
|
||||||
"${USER_HOME}/.local/bin/thorium-browser"
|
"${USER_HOME}/.local/bin/thorium-browser"
|
||||||
)
|
)
|
||||||
|
|
||||||
local found=false
|
local found=false
|
||||||
for path in "${alt_paths[@]}"; do
|
for path in "${alt_paths[@]}"; do
|
||||||
if [[ -x $path ]]; then
|
if [[ -x $path ]]; then
|
||||||
BROWSER_COMMAND="$path"
|
BROWSER_COMMAND="$path"
|
||||||
echo "✓ Found Thorium browser at: $path"
|
echo "✓ Found Thorium browser at: $path"
|
||||||
found=true
|
found=true
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $found != true ]]; then
|
if [[ $found != true ]]; then
|
||||||
echo "Error: Thorium browser not found!"
|
echo "Error: Thorium browser not found!"
|
||||||
echo "Please install Thorium browser first or ensure it's in your PATH."
|
echo "Please install Thorium browser first or ensure it's in your PATH."
|
||||||
echo ""
|
echo ""
|
||||||
echo "You can install Thorium browser from:"
|
echo "You can install Thorium browser from:"
|
||||||
echo "https://thorium.rocks/"
|
echo "https://thorium.rocks/"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local continue_anyway=false
|
local continue_anyway=false
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
read -p "Continue anyway? The service will be created but may fail to start (y/N): " -n 1 -r
|
read -p "Continue anyway? The service will be created but may fail to start (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
continue_anyway=true
|
continue_anyway=true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)"
|
echo "Auto-continuing anyway - service will be created but may fail to start (use --interactive to prompt)"
|
||||||
continue_anyway=true
|
continue_anyway=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $continue_anyway != true ]]; then
|
if [[ $continue_anyway != true ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)"
|
echo "✓ Thorium browser found: $(which $BROWSER_COMMAND)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the browser launcher script
|
# Function to create the browser launcher script
|
||||||
create_launcher_script() {
|
create_launcher_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Creating Browser Launcher Script..."
|
echo "2. Creating Browser Launcher Script..."
|
||||||
echo "====================================="
|
echo "====================================="
|
||||||
|
|
||||||
local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh"
|
local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh"
|
||||||
|
|
||||||
cat >"$launcher_script" <<EOF
|
cat > "$launcher_script" << EOF
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Thorium browser launcher for Fitatu website
|
# Thorium browser launcher for Fitatu website
|
||||||
# Created by setup_thorium_startup.sh on $(date)
|
# Created by setup_thorium_startup.sh on $(date)
|
||||||
@ -163,24 +163,24 @@ else
|
|||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x "$launcher_script"
|
chmod +x "$launcher_script"
|
||||||
echo "✓ Created launcher script: $launcher_script"
|
echo "✓ Created launcher script: $launcher_script"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create systemd service for user session
|
# Function to create systemd service for user session
|
||||||
create_user_systemd_service() {
|
create_user_systemd_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Creating User Systemd Service..."
|
echo "3. Creating User Systemd Service..."
|
||||||
echo "=================================="
|
echo "=================================="
|
||||||
|
|
||||||
local user_systemd_dir="$USER_HOME/.config/systemd/user"
|
local user_systemd_dir="$USER_HOME/.config/systemd/user"
|
||||||
local service_file="$user_systemd_dir/thorium-fitatu-startup.service"
|
local service_file="$user_systemd_dir/thorium-fitatu-startup.service"
|
||||||
|
|
||||||
# Create user systemd directory
|
# Create user systemd directory
|
||||||
sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir"
|
sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir"
|
||||||
|
|
||||||
# Create the service file
|
# 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]
|
[Unit]
|
||||||
Description=Launch Thorium Browser with Fitatu on Startup
|
Description=Launch Thorium Browser with Fitatu on Startup
|
||||||
After=graphical-session.target
|
After=graphical-session.target
|
||||||
@ -205,18 +205,18 @@ TimeoutStartSec=120
|
|||||||
WantedBy=default.target
|
WantedBy=default.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created user systemd service: $service_file"
|
echo "✓ Created user systemd service: $service_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create system-wide systemd service (alternative approach)
|
# Function to create system-wide systemd service (alternative approach)
|
||||||
create_system_systemd_service() {
|
create_system_systemd_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Creating System Systemd Service..."
|
echo "4. Creating System Systemd Service..."
|
||||||
echo "===================================="
|
echo "===================================="
|
||||||
|
|
||||||
local service_file="/etc/systemd/system/thorium-fitatu-startup.service"
|
local service_file="/etc/systemd/system/thorium-fitatu-startup.service"
|
||||||
|
|
||||||
cat >"$service_file" <<EOF
|
cat > "$service_file" << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Launch Thorium Browser with Fitatu on Startup
|
Description=Launch Thorium Browser with Fitatu on Startup
|
||||||
After=multi-user.target network-online.target
|
After=multi-user.target network-online.target
|
||||||
@ -243,23 +243,23 @@ TimeoutStartSec=180
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created system systemd service: $service_file"
|
echo "✓ Created system systemd service: $service_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create autostart desktop entry (additional method)
|
# Function to create autostart desktop entry (additional method)
|
||||||
create_autostart_entry() {
|
create_autostart_entry() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "5. Creating Autostart Desktop Entry..."
|
echo "5. Creating Autostart Desktop Entry..."
|
||||||
echo "====================================="
|
echo "====================================="
|
||||||
|
|
||||||
local autostart_dir="$USER_HOME/.config/autostart"
|
local autostart_dir="$USER_HOME/.config/autostart"
|
||||||
local desktop_file="$autostart_dir/thorium-fitatu.desktop"
|
local desktop_file="$autostart_dir/thorium-fitatu.desktop"
|
||||||
|
|
||||||
# Create autostart directory
|
# Create autostart directory
|
||||||
sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir"
|
sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir"
|
||||||
|
|
||||||
# Create desktop entry
|
# 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]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=Thorium Fitatu Startup
|
Name=Thorium Fitatu Startup
|
||||||
@ -274,45 +274,45 @@ Terminal=false
|
|||||||
Categories=Network;WebBrowser;
|
Categories=Network;WebBrowser;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "✓ Created autostart desktop entry: $desktop_file"
|
echo "✓ Created autostart desktop entry: $desktop_file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create i3 config autostart entry
|
# Function to create i3 config autostart entry
|
||||||
create_i3_autostart() {
|
create_i3_autostart() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Creating i3 Config Autostart Entry..."
|
echo "6. Creating i3 Config Autostart Entry..."
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
|
|
||||||
local i3_config="$USER_HOME/.config/i3/config"
|
local i3_config="$USER_HOME/.config/i3/config"
|
||||||
local i3_config_dir="$USER_HOME/.config/i3"
|
local i3_config_dir="$USER_HOME/.config/i3"
|
||||||
|
|
||||||
# Create i3 config directory if it doesn't exist
|
# Create i3 config directory if it doesn't exist
|
||||||
sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir"
|
sudo -u "${SUDO_USER}" mkdir -p "$i3_config_dir"
|
||||||
|
|
||||||
# Check if i3 config exists
|
# Check if i3 config exists
|
||||||
if [[ -f $i3_config ]]; then
|
if [[ -f $i3_config ]]; then
|
||||||
# Check if autostart entry already exists
|
# Check if autostart entry already exists
|
||||||
if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then
|
if ! sudo -u "${SUDO_USER}" grep -q "thorium-fitatu-launcher" "$i3_config"; then
|
||||||
# Add autostart entry to i3 config
|
# Add autostart entry to i3 config
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'"
|
sudo -u "${SUDO_USER}" bash -c "echo '' >> '$i3_config'"
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'"
|
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-start Thorium browser with Fitatu' >> '$i3_config'"
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'"
|
sudo -u "${SUDO_USER}" bash -c "echo 'exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh' >> '$i3_config'"
|
||||||
echo "✓ Added autostart entry to i3 config: $i3_config"
|
echo "✓ Added autostart entry to i3 config: $i3_config"
|
||||||
else
|
else
|
||||||
echo "✓ Autostart entry already exists in i3 config"
|
echo "✓ Autostart entry already exists in i3 config"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Warning: i3 config file not found at $i3_config"
|
echo "Warning: i3 config file not found at $i3_config"
|
||||||
echo "You may need to manually add the following line to your i3 config:"
|
echo "You may need to manually add the following line to your i3 config:"
|
||||||
echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh"
|
echo "exec --no-startup-id /usr/local/bin/thorium-fitatu-launcher.sh"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create a script to enable user service after login
|
# Function to create a script to enable user service after login
|
||||||
create_user_enable_script() {
|
create_user_enable_script() {
|
||||||
local enable_script="$USER_HOME/.config/thorium-enable-service.sh"
|
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
|
#!/bin/bash
|
||||||
# Script to enable thorium-fitatu-startup user service
|
# Script to enable thorium-fitatu-startup user service
|
||||||
# This runs once to enable the service, then removes itself
|
# This runs once to enable the service, then removes itself
|
||||||
@ -325,110 +325,110 @@ systemctl --user enable thorium-fitatu-startup.service
|
|||||||
rm "$0"
|
rm "$0"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
sudo -u "${SUDO_USER}" chmod +x "$enable_script"
|
sudo -u "${SUDO_USER}" chmod +x "$enable_script"
|
||||||
|
|
||||||
# Add to user's .bashrc to run on next login
|
# Add to user's .bashrc to run on next login
|
||||||
local bashrc="$USER_HOME/.bashrc"
|
local bashrc="$USER_HOME/.bashrc"
|
||||||
if [[ -f $bashrc ]]; then
|
if [[ -f $bashrc ]]; then
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo '' >> '$bashrc'"
|
sudo -u "${SUDO_USER}" bash -c "echo '' >> '$bashrc'"
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-enable thorium service (temporary)' >> '$bashrc'"
|
sudo -u "${SUDO_USER}" bash -c "echo '# Auto-enable thorium service (temporary)' >> '$bashrc'"
|
||||||
sudo -u "${SUDO_USER}" bash -c "echo '[[ -x ~/.config/thorium-enable-service.sh ]] && ~/.config/thorium-enable-service.sh' >> '$bashrc'"
|
sudo -u "${SUDO_USER}" bash -c "echo '[[ -x ~/.config/thorium-enable-service.sh ]] && ~/.config/thorium-enable-service.sh' >> '$bashrc'"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to enable services
|
# Function to enable services
|
||||||
enable_services() {
|
enable_services() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "7. Enabling Services..."
|
echo "7. Enabling Services..."
|
||||||
echo "======================"
|
echo "======================"
|
||||||
|
|
||||||
# Reload systemd daemon
|
# Reload systemd daemon
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
echo "✓ System daemon reloaded"
|
echo "✓ System daemon reloaded"
|
||||||
|
|
||||||
# Enable system service
|
# Enable system service
|
||||||
systemctl enable thorium-fitatu-startup.service
|
systemctl enable thorium-fitatu-startup.service
|
||||||
echo "✓ System service enabled"
|
echo "✓ System service enabled"
|
||||||
|
|
||||||
# Enable lingering for the user (allows user services to run without login)
|
# Enable lingering for the user (allows user services to run without login)
|
||||||
loginctl enable-linger "${SUDO_USER}"
|
loginctl enable-linger "${SUDO_USER}"
|
||||||
echo "✓ User lingering enabled"
|
echo "✓ User lingering enabled"
|
||||||
|
|
||||||
# Create a script to enable user service after login
|
# Create a script to enable user service after login
|
||||||
create_user_enable_script
|
create_user_enable_script
|
||||||
echo "✓ User service will be enabled on next login"
|
echo "✓ User service will be enabled on next login"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to test the setup
|
# Function to test the setup
|
||||||
test_setup() {
|
test_setup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "8. Testing Setup..."
|
echo "8. Testing Setup..."
|
||||||
echo "=================="
|
echo "=================="
|
||||||
|
|
||||||
local run_test=true
|
local run_test=true
|
||||||
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
echo "Would you like to test the browser launcher now?"
|
echo "Would you like to test the browser launcher now?"
|
||||||
read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r
|
read -p "Test launch Thorium browser with Fitatu? (y/N): " -n 1 -r
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
run_test=false
|
run_test=false
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Auto-testing the browser launcher (use --interactive to prompt)"
|
echo "Auto-testing the browser launcher (use --interactive to prompt)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $run_test == "true" ]]; then
|
if [[ $run_test == "true" ]]; then
|
||||||
echo "Testing browser launch..."
|
echo "Testing browser launch..."
|
||||||
echo "Note: This will open Thorium browser with Fitatu website"
|
echo "Note: This will open Thorium browser with Fitatu website"
|
||||||
|
|
||||||
# Test the launcher immediately
|
# Test the launcher immediately
|
||||||
if /usr/local/bin/thorium-fitatu-launcher.sh; then
|
if /usr/local/bin/thorium-fitatu-launcher.sh; then
|
||||||
echo "✓ Test launch completed successfully"
|
echo "✓ Test launch completed successfully"
|
||||||
else
|
else
|
||||||
echo "✗ Test launch failed"
|
echo "✗ Test launch failed"
|
||||||
echo "Check that Thorium browser is properly installed and accessible"
|
echo "Check that Thorium browser is properly installed and accessible"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Skipping test launch"
|
echo "Skipping test launch"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to show usage instructions
|
# Function to show usage instructions
|
||||||
show_instructions() {
|
show_instructions() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Thorium Browser Auto-Startup Setup Complete"
|
echo "Thorium Browser Auto-Startup Setup Complete"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "Summary:"
|
echo "Summary:"
|
||||||
echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh"
|
echo "✓ Launcher script created: /usr/local/bin/thorium-fitatu-launcher.sh"
|
||||||
echo "✓ System service created: thorium-fitatu-startup.service"
|
echo "✓ System service created: thorium-fitatu-startup.service"
|
||||||
echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service"
|
echo "✓ User service created: ~/.config/systemd/user/thorium-fitatu-startup.service"
|
||||||
echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop"
|
echo "✓ Autostart entry created: ~/.config/autostart/thorium-fitatu.desktop"
|
||||||
echo "✓ i3 autostart entry added to: ~/.config/i3/config"
|
echo "✓ i3 autostart entry added to: ~/.config/i3/config"
|
||||||
echo "✓ Services enabled for automatic startup"
|
echo "✓ Services enabled for automatic startup"
|
||||||
echo ""
|
echo ""
|
||||||
echo "The system will now:"
|
echo "The system will now:"
|
||||||
echo "• Launch Thorium browser with $TARGET_URL on every startup"
|
echo "• Launch Thorium browser with $TARGET_URL on every startup"
|
||||||
echo "• Use multiple methods to ensure reliable startup"
|
echo "• Use multiple methods to ensure reliable startup"
|
||||||
echo "• Wait for desktop environment to be ready before launching"
|
echo "• Wait for desktop environment to be ready before launching"
|
||||||
echo "• User service will be enabled automatically on next login"
|
echo "• User service will be enabled automatically on next login"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To check status:"
|
echo "To check status:"
|
||||||
echo " systemctl status thorium-fitatu-startup.service"
|
echo " systemctl status thorium-fitatu-startup.service"
|
||||||
echo " systemctl --user status thorium-fitatu-startup.service (after login)"
|
echo " systemctl --user status thorium-fitatu-startup.service (after login)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To view logs:"
|
echo "To view logs:"
|
||||||
echo " journalctl -u thorium-fitatu-startup.service"
|
echo " journalctl -u thorium-fitatu-startup.service"
|
||||||
echo " journalctl --user -u thorium-fitatu-startup.service"
|
echo " journalctl --user -u thorium-fitatu-startup.service"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To disable (if needed):"
|
echo "To disable (if needed):"
|
||||||
echo " sudo systemctl disable thorium-fitatu-startup.service"
|
echo " sudo systemctl disable thorium-fitatu-startup.service"
|
||||||
echo " systemctl --user disable thorium-fitatu-startup.service"
|
echo " systemctl --user disable thorium-fitatu-startup.service"
|
||||||
echo " rm ~/.config/autostart/thorium-fitatu.desktop"
|
echo " rm ~/.config/autostart/thorium-fitatu.desktop"
|
||||||
echo ""
|
echo ""
|
||||||
echo "IMPORTANT: Browser will launch automatically on next reboot!"
|
echo "IMPORTANT: Browser will launch automatically on next reboot!"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
|
|||||||
@ -11,100 +11,100 @@ HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__"
|
|||||||
|
|
||||||
# Log with timestamp (hosts-file-monitor specific)
|
# Log with timestamp (hosts-file-monitor specific)
|
||||||
log_message() {
|
log_message() {
|
||||||
printf '%s [hosts-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2
|
printf '%s [hosts-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if hosts file needs restoration
|
# Function to check if hosts file needs restoration
|
||||||
needs_restoration() {
|
needs_restoration() {
|
||||||
# Check if file exists
|
# Check if file exists
|
||||||
if [[ ! -f $HOSTS_FILE ]]; then
|
if [[ ! -f $HOSTS_FILE ]]; then
|
||||||
return 0 # File missing, needs restoration
|
return 0 # File missing, needs restoration
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if file is empty or too small (less than 1000 lines indicates tampering)
|
# Check if file is empty or too small (less than 1000 lines indicates tampering)
|
||||||
local line_count
|
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
|
if [[ $line_count -lt 1000 ]]; then
|
||||||
return 0 # File too small, likely tampered with
|
return 0 # File too small, likely tampered with
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if our custom entries are missing
|
# 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
|
return 0 # Our custom entries missing, needs restoration
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if StevenBlack entries are missing
|
# 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
|
return 0 # StevenBlack entries missing, needs restoration
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1 # File seems intact
|
return 1 # File seems intact
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to restore hosts file
|
# Function to restore hosts file
|
||||||
restore_hosts_file() {
|
restore_hosts_file() {
|
||||||
log_message "Hosts file modification detected - initiating restoration"
|
log_message "Hosts file modification detected - initiating restoration"
|
||||||
|
|
||||||
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
||||||
log_message "Running hosts installation script: $HOSTS_INSTALL_SCRIPT"
|
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"
|
log_message "Hosts file restoration completed successfully"
|
||||||
else
|
else
|
||||||
log_message "Hosts file restoration failed with exit code $?"
|
log_message "Hosts file restoration failed with exit code $?"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT"
|
log_message "ERROR: Hosts install script not found at $HOSTS_INSTALL_SCRIPT"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to monitor with inotifywait
|
# Function to monitor with inotifywait
|
||||||
monitor_with_inotify() {
|
monitor_with_inotify() {
|
||||||
log_message "Starting hosts file monitoring with inotify"
|
log_message "Starting hosts file monitoring with inotify"
|
||||||
|
|
||||||
# Monitor the hosts file and its directory for various events
|
# 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
|
while read -r file event time; do
|
||||||
# Check if the event is related to our hosts file
|
# Check if the event is related to our hosts file
|
||||||
if [[ $file == "$HOSTS_FILE" ]] || [[ $file == "/etc/hosts" ]]; then
|
if [[ $file == "$HOSTS_FILE" ]] || [[ $file == "/etc/hosts" ]]; then
|
||||||
log_message "Event detected: $event on $file at $time"
|
log_message "Event detected: $event on $file at $time"
|
||||||
|
|
||||||
# Small delay to avoid rapid-fire events
|
# Small delay to avoid rapid-fire events
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# Check if restoration is needed
|
# Check if restoration is needed
|
||||||
if needs_restoration; then
|
if needs_restoration; then
|
||||||
restore_hosts_file
|
restore_hosts_file
|
||||||
else
|
else
|
||||||
log_message "Hosts file check passed - no restoration needed"
|
log_message "Hosts file check passed - no restoration needed"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to monitor with polling (fallback)
|
# Function to monitor with polling (fallback)
|
||||||
monitor_with_polling() {
|
monitor_with_polling() {
|
||||||
log_message "Starting hosts file monitoring with polling (fallback method)"
|
log_message "Starting hosts file monitoring with polling (fallback method)"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
if needs_restoration; then
|
if needs_restoration; then
|
||||||
restore_hosts_file
|
restore_hosts_file
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check every 30 seconds
|
# Check every 30 seconds
|
||||||
sleep 30
|
sleep 30
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
log_message "=== Hosts File Monitor Started ==="
|
log_message "=== Hosts File Monitor Started ==="
|
||||||
|
|
||||||
# Check if inotify-tools is available
|
# 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"
|
log_message "Using inotify for file monitoring"
|
||||||
monitor_with_inotify
|
monitor_with_inotify
|
||||||
else
|
else
|
||||||
log_message "inotify-tools not available, using polling method"
|
log_message "inotify-tools not available, using polling method"
|
||||||
log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools"
|
log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools"
|
||||||
monitor_with_polling
|
monitor_with_polling
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -12,106 +12,106 @@ CHECK_INTERVAL=30
|
|||||||
|
|
||||||
# Log with timestamp (shutdown-timer-monitor specific)
|
# Log with timestamp (shutdown-timer-monitor specific)
|
||||||
log_message() {
|
log_message() {
|
||||||
printf '%s [shutdown-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2
|
printf '%s [shutdown-monitor] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if timer needs to be re-enabled
|
# Function to check if timer needs to be re-enabled
|
||||||
timer_needs_restoration() {
|
timer_needs_restoration() {
|
||||||
# Check if timer is enabled
|
# 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"
|
log_message "Timer $TIMER_NAME is not enabled"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if timer is active
|
# 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"
|
log_message "Timer $TIMER_NAME is not active"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if timer unit file exists
|
# Check if timer unit file exists
|
||||||
if [[ ! -f "/etc/systemd/system/$TIMER_NAME" ]]; then
|
if [[ ! -f "/etc/systemd/system/$TIMER_NAME" ]]; then
|
||||||
log_message "Timer unit file missing: /etc/systemd/system/$TIMER_NAME"
|
log_message "Timer unit file missing: /etc/systemd/system/$TIMER_NAME"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if service unit file exists
|
# Check if service unit file exists
|
||||||
if [[ ! -f "/etc/systemd/system/$SERVICE_NAME" ]]; then
|
if [[ ! -f "/etc/systemd/system/$SERVICE_NAME" ]]; then
|
||||||
log_message "Service unit file missing: /etc/systemd/system/$SERVICE_NAME"
|
log_message "Service unit file missing: /etc/systemd/system/$SERVICE_NAME"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if check script exists
|
# Check if check script exists
|
||||||
if [[ ! -f "/usr/local/bin/day-specific-shutdown-check.sh" ]]; then
|
if [[ ! -f "/usr/local/bin/day-specific-shutdown-check.sh" ]]; then
|
||||||
log_message "Check script missing: /usr/local/bin/day-specific-shutdown-check.sh"
|
log_message "Check script missing: /usr/local/bin/day-specific-shutdown-check.sh"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1 # Timer is properly configured
|
return 1 # Timer is properly configured
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to restore timer
|
# Function to restore timer
|
||||||
restore_timer() {
|
restore_timer() {
|
||||||
log_message "Shutdown timer tampering detected - initiating restoration"
|
log_message "Shutdown timer tampering detected - initiating restoration"
|
||||||
|
|
||||||
# Reload systemd daemon in case unit files were modified
|
# Reload systemd daemon in case unit files were modified
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
# Re-enable timer if disabled
|
# 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"
|
log_message "Re-enabling $TIMER_NAME"
|
||||||
systemctl enable "$TIMER_NAME" 2>/dev/null || true
|
systemctl enable "$TIMER_NAME" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Re-start timer if not active
|
# 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"
|
log_message "Re-starting $TIMER_NAME"
|
||||||
systemctl start "$TIMER_NAME" 2>/dev/null || true
|
systemctl start "$TIMER_NAME" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify restoration
|
# 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"
|
log_message "Timer restoration completed successfully"
|
||||||
else
|
else
|
||||||
log_message "WARNING: Timer restoration may have failed"
|
log_message "WARNING: Timer restoration may have failed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to monitor timer with systemd events
|
# Function to monitor timer with systemd events
|
||||||
monitor_with_dbus() {
|
monitor_with_dbus() {
|
||||||
log_message "Starting shutdown timer monitoring with D-Bus events"
|
log_message "Starting shutdown timer monitoring with D-Bus events"
|
||||||
|
|
||||||
# Use busctl to monitor systemd unit changes
|
# Use busctl to monitor systemd unit changes
|
||||||
# Fall back to polling if this fails
|
# 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
|
# 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
|
while read -r line; do
|
||||||
# Check if the line mentions our timer
|
# Check if the line mentions our timer
|
||||||
if echo "$line" | grep -q "$TIMER_NAME\|$SERVICE_NAME"; then
|
if echo "$line" | grep -q "$TIMER_NAME\|$SERVICE_NAME"; then
|
||||||
log_message "Systemd event detected for shutdown timer"
|
log_message "Systemd event detected for shutdown timer"
|
||||||
sleep 2
|
sleep 2
|
||||||
if timer_needs_restoration; then
|
if timer_needs_restoration; then
|
||||||
restore_timer
|
restore_timer
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
log_message "busctl not available, falling back to polling"
|
log_message "busctl not available, falling back to polling"
|
||||||
monitor_with_polling
|
monitor_with_polling
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to monitor with polling (primary method for reliability)
|
# Function to monitor with polling (primary method for reliability)
|
||||||
monitor_with_polling() {
|
monitor_with_polling() {
|
||||||
log_message "Starting shutdown timer monitoring with polling (interval: ${CHECK_INTERVAL}s)"
|
log_message "Starting shutdown timer monitoring with polling (interval: ${CHECK_INTERVAL}s)"
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
if timer_needs_restoration; then
|
if timer_needs_restoration; then
|
||||||
restore_timer
|
restore_timer
|
||||||
fi
|
fi
|
||||||
sleep "$CHECK_INTERVAL"
|
sleep "$CHECK_INTERVAL"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
@ -121,10 +121,10 @@ log_message "Monitoring service: $SERVICE_NAME"
|
|||||||
|
|
||||||
# Initial check
|
# Initial check
|
||||||
if timer_needs_restoration; then
|
if timer_needs_restoration; then
|
||||||
log_message "Initial check: Timer needs restoration"
|
log_message "Initial check: Timer needs restoration"
|
||||||
restore_timer
|
restore_timer
|
||||||
else
|
else
|
||||||
log_message "Initial check: Timer is properly configured"
|
log_message "Initial check: Timer is properly configured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use polling for reliability (D-Bus monitoring can miss events)
|
# Use polling for reliability (D-Bus monitoring can miss events)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -9,10 +9,10 @@ WATCHDOG_SCRIPT="$GUARDIAN_DIR/watchdog.sh"
|
|||||||
mkdir -p "$GUARDIAN_DIR"
|
mkdir -p "$GUARDIAN_DIR"
|
||||||
|
|
||||||
# Log that we're starting
|
# 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
|
# Create persistent watchdog script that runs independently of module state
|
||||||
cat >"$WATCHDOG_SCRIPT" <<'WATCHDOG'
|
cat > "$WATCHDOG_SCRIPT" << 'WATCHDOG'
|
||||||
#!/system/bin/sh
|
#!/system/bin/sh
|
||||||
# Secondary watchdog - runs independently of module state
|
# Secondary watchdog - runs independently of module state
|
||||||
# Even if module is "disabled" in Magisk UI, this keeps running and undoes it
|
# Even if module is "disabled" in Magisk UI, this keeps running and undoes it
|
||||||
@ -59,5 +59,5 @@ WATCHDOG
|
|||||||
chmod 755 "$WATCHDOG_SCRIPT"
|
chmod 755 "$WATCHDOG_SCRIPT"
|
||||||
|
|
||||||
# Start watchdog as a separate background process
|
# Start watchdog as a separate background process
|
||||||
nohup sh "$WATCHDOG_SCRIPT" >/dev/null 2>&1 &
|
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"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Watchdog started" >> "$GUARDIAN_DIR/guardian.log"
|
||||||
|
|||||||
@ -20,83 +20,83 @@ REMOVE_FILE="$MODULE_DIR/remove"
|
|||||||
mkdir -p "$GUARDIAN_DIR"
|
mkdir -p "$GUARDIAN_DIR"
|
||||||
|
|
||||||
log() {
|
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
|
# Initialize control file if not exists
|
||||||
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" >"$CONTROL_FILE"
|
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" > "$CONTROL_FILE"
|
||||||
|
|
||||||
log "=== Android Guardian starting ==="
|
log "=== Android Guardian starting ==="
|
||||||
|
|
||||||
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
|
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
|
||||||
is_enabled() {
|
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
|
# Function to protect module from being disabled via Magisk UI
|
||||||
protect_module() {
|
protect_module() {
|
||||||
# Remove disable file if someone tried to disable via Magisk
|
# Remove disable file if someone tried to disable via Magisk
|
||||||
if [ -f "$DISABLE_FILE" ]; then
|
if [ -f "$DISABLE_FILE" ]; then
|
||||||
log "Module disable attempt detected via Magisk UI! Re-enabling..."
|
log "Module disable attempt detected via Magisk UI! Re-enabling..."
|
||||||
rm -f "$DISABLE_FILE"
|
rm -f "$DISABLE_FILE"
|
||||||
log "Module re-enabled"
|
log "Module re-enabled"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove remove file if someone tried to uninstall via Magisk
|
# Remove remove file if someone tried to uninstall via Magisk
|
||||||
if [ -f "$REMOVE_FILE" ]; then
|
if [ -f "$REMOVE_FILE" ]; then
|
||||||
log "Module removal attempt detected via Magisk UI! Blocking..."
|
log "Module removal attempt detected via Magisk UI! Blocking..."
|
||||||
rm -f "$REMOVE_FILE"
|
rm -f "$REMOVE_FILE"
|
||||||
log "Module removal blocked"
|
log "Module removal blocked"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to restore hosts file if tampered
|
# Function to restore hosts file if tampered
|
||||||
protect_hosts() {
|
protect_hosts() {
|
||||||
if [ -f "$HOSTS_BACKUP" ]; then
|
if [ -f "$HOSTS_BACKUP" ]; then
|
||||||
current_hash=$(md5sum /system/etc/hosts 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)
|
backup_hash=$(md5sum "$HOSTS_BACKUP" 2> /dev/null | cut -d' ' -f1)
|
||||||
|
|
||||||
if [ "$current_hash" != "$backup_hash" ]; then
|
if [ "$current_hash" != "$backup_hash" ]; then
|
||||||
log "Hosts file tampering detected! Restoring..."
|
log "Hosts file tampering detected! Restoring..."
|
||||||
cp "$HOSTS_BACKUP" "$MODDIR/system/etc/hosts"
|
cp "$HOSTS_BACKUP" "$MODDIR/system/etc/hosts"
|
||||||
log "Hosts file restored"
|
log "Hosts file restored"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to uninstall blocked apps
|
# Function to uninstall blocked apps
|
||||||
check_blocked_apps() {
|
check_blocked_apps() {
|
||||||
if [ ! -f "$BLOCKED_APPS_FILE" ]; then
|
if [ ! -f "$BLOCKED_APPS_FILE" ]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while IFS= read -r package || [ -n "$package" ]; do
|
while IFS= read -r package || [ -n "$package" ]; do
|
||||||
# Skip comments and empty lines
|
# Skip comments and empty lines
|
||||||
case "$package" in
|
case "$package" in
|
||||||
\#* | "") continue ;;
|
\#* | "") continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Check if package is installed
|
# 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..."
|
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
|
fi
|
||||||
done <"$BLOCKED_APPS_FILE"
|
done < "$BLOCKED_APPS_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main monitoring loop - runs every 5 seconds for faster protection
|
# Main monitoring loop - runs every 5 seconds for faster protection
|
||||||
while true; do
|
while true; do
|
||||||
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
|
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
|
||||||
# This ensures only ADB can control the guardian
|
# This ensures only ADB can control the guardian
|
||||||
protect_module
|
protect_module
|
||||||
|
|
||||||
if is_enabled; then
|
if is_enabled; then
|
||||||
protect_hosts
|
protect_hosts
|
||||||
check_blocked_apps
|
check_blocked_apps
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check every 5 seconds (faster response to disable attempts)
|
# Check every 5 seconds (faster response to disable attempts)
|
||||||
sleep 5
|
sleep 5
|
||||||
done &
|
done &
|
||||||
|
|
||||||
log "Guardian service started (PID: $!)"
|
log "Guardian service started (PID: $!)"
|
||||||
|
|||||||
@ -4,12 +4,12 @@ GUARDIAN_DIR="/data/adb/android_guardian"
|
|||||||
|
|
||||||
# Only allow uninstall if control file says DISABLED
|
# Only allow uninstall if control file says DISABLED
|
||||||
if [ -f "$GUARDIAN_DIR/control" ]; then
|
if [ -f "$GUARDIAN_DIR/control" ]; then
|
||||||
status=$(cat "$GUARDIAN_DIR/control")
|
status=$(cat "$GUARDIAN_DIR/control")
|
||||||
if [ "$status" != "DISABLED" ]; then
|
if [ "$status" != "DISABLED" ]; then
|
||||||
echo "Guardian is still enabled! Use ADB to disable first:"
|
echo "Guardian is still enabled! Use ADB to disable first:"
|
||||||
echo " adb shell 'echo DISABLED > /data/adb/android_guardian/control'"
|
echo " adb shell 'echo DISABLED > /data/adb/android_guardian/control'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean up guardian data
|
# Clean up guardian data
|
||||||
|
|||||||
@ -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")
|
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() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage:
|
Usage:
|
||||||
$(basename "$0") [OPTIONS] PATH
|
$(basename "$0") [OPTIONS] PATH
|
||||||
|
|
||||||
@ -47,193 +47,193 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_ffmpeg() {
|
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
|
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
get_video_extensions_except() {
|
get_video_extensions_except() {
|
||||||
local exclude="$1"
|
local exclude="$1"
|
||||||
local exts=()
|
local exts=()
|
||||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ ${ext,,} != "${exclude,,}" ]]; then
|
if [[ ${ext,,} != "${exclude,,}" ]]; then
|
||||||
exts+=("$ext")
|
exts+=("$ext")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo "${exts[@]}"
|
echo "${exts[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
is_video_file() {
|
is_video_file() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
local ext="${file##*.}"
|
local ext="${file##*.}"
|
||||||
ext="${ext,,}" # lowercase
|
ext="${ext,,}" # lowercase
|
||||||
|
|
||||||
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ $ext == "${video_ext,,}" ]]; then
|
if [[ $ext == "${video_ext,,}" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
convert_video() {
|
convert_video() {
|
||||||
local input_file="$1"
|
local input_file="$1"
|
||||||
local output_file="${input_file%.*}.${TARGET_FORMAT}"
|
local output_file="${input_file%.*}.${TARGET_FORMAT}"
|
||||||
|
|
||||||
# Skip if output already exists
|
# Skip if output already exists
|
||||||
if [[ -f $output_file ]]; then
|
if [[ -f $output_file ]]; then
|
||||||
log "Skipping '$input_file': output '$output_file' already exists"
|
log "Skipping '$input_file': output '$output_file' already exists"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Converting '$input_file' -> '$output_file'"
|
log "Converting '$input_file' -> '$output_file'"
|
||||||
|
|
||||||
local ffmpeg_args=()
|
local ffmpeg_args=()
|
||||||
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
|
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
|
||||||
|
|
||||||
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
||||||
# H.264 codec for video and AAC for audio (maximum compatibility)
|
# H.264 codec for video and AAC for audio (maximum compatibility)
|
||||||
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
|
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
|
||||||
ffmpeg_args+=(-c:a aac -b:a 192k)
|
ffmpeg_args+=(-c:a aac -b:a 192k)
|
||||||
ffmpeg_args+=(-movflags +faststart)
|
ffmpeg_args+=(-movflags +faststart)
|
||||||
elif [[ $TARGET_FORMAT == "webm" ]]; then
|
elif [[ $TARGET_FORMAT == "webm" ]]; then
|
||||||
# VP9 codec for video and Opus for audio
|
# VP9 codec for video and Opus for audio
|
||||||
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
|
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
|
||||||
ffmpeg_args+=(-c:a libopus -b:a 128k)
|
ffmpeg_args+=(-c:a libopus -b:a 128k)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ffmpeg_args+=("$output_file")
|
ffmpeg_args+=("$output_file")
|
||||||
|
|
||||||
if ffmpeg "${ffmpeg_args[@]}"; then
|
if ffmpeg "${ffmpeg_args[@]}"; then
|
||||||
log "Successfully converted '$input_file'"
|
log "Successfully converted '$input_file'"
|
||||||
|
|
||||||
if [[ $DELETE_ORIGINAL == true ]]; then
|
if [[ $DELETE_ORIGINAL == true ]]; then
|
||||||
log "Deleting original: '$input_file'"
|
log "Deleting original: '$input_file'"
|
||||||
rm "$input_file"
|
rm "$input_file"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "Error converting '$input_file'"
|
log "Error converting '$input_file'"
|
||||||
[[ -f $output_file ]] && rm "$output_file"
|
[[ -f $output_file ]] && rm "$output_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
process_directory() {
|
process_directory() {
|
||||||
local dir="$1"
|
local dir="$1"
|
||||||
local count=0
|
local count=0
|
||||||
local failed=0
|
local failed=0
|
||||||
|
|
||||||
log "Searching for video files in '$dir'..."
|
log "Searching for video files in '$dir'..."
|
||||||
|
|
||||||
# Build find command dynamically
|
# Build find command dynamically
|
||||||
local find_args=(-type f \()
|
local find_args=(-type f \()
|
||||||
local first=true
|
local first=true
|
||||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ ${ext,,} != "${TARGET_FORMAT,,}" ]]; then
|
if [[ ${ext,,} != "${TARGET_FORMAT,,}" ]]; then
|
||||||
if [[ $first == true ]]; then
|
if [[ $first == true ]]; then
|
||||||
first=false
|
first=false
|
||||||
else
|
else
|
||||||
find_args+=(-o)
|
find_args+=(-o)
|
||||||
fi
|
fi
|
||||||
find_args+=(-iname "*.$ext")
|
find_args+=(-iname "*.$ext")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
find_args+=(\) -print0)
|
find_args+=(\) -print0)
|
||||||
|
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
((count++)) || true
|
((count++)) || true
|
||||||
if ! convert_video "$file"; then
|
if ! convert_video "$file"; then
|
||||||
((failed++)) || true
|
((failed++)) || true
|
||||||
fi
|
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"
|
log "Processed $count video file(s), $failed failed"
|
||||||
|
|
||||||
if [[ $count -eq 0 ]]; then
|
if [[ $count -eq 0 ]]; then
|
||||||
log "No video files found in '$dir'"
|
log "No video files found in '$dir'"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
while getopts ":f:c:p:dh" opt; do
|
while getopts ":f:c:p:dh" opt; do
|
||||||
case "$opt" in
|
case "$opt" in
|
||||||
f)
|
f)
|
||||||
TARGET_FORMAT="${OPTARG,,}"
|
TARGET_FORMAT="${OPTARG,,}"
|
||||||
if [[ $TARGET_FORMAT != "mp4" && $TARGET_FORMAT != "webm" ]]; then
|
if [[ $TARGET_FORMAT != "mp4" && $TARGET_FORMAT != "webm" ]]; then
|
||||||
echo "Error: Format must be 'mp4' or 'webm'" >&2
|
echo "Error: Format must be 'mp4' or 'webm'" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
c) CRF="$OPTARG" ;;
|
c) CRF="$OPTARG" ;;
|
||||||
p) PRESET="$OPTARG" ;;
|
p) PRESET="$OPTARG" ;;
|
||||||
d) DELETE_ORIGINAL=true ;;
|
d) DELETE_ORIGINAL=true ;;
|
||||||
h)
|
h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
:)
|
:)
|
||||||
echo "Error: Option -$OPTARG requires an argument." >&2
|
echo "Error: Option -$OPTARG requires an argument." >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
\?)
|
\?)
|
||||||
echo "Error: Invalid option -$OPTARG" >&2
|
echo "Error: Invalid option -$OPTARG" >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
shift $((OPTIND - 1))
|
shift $((OPTIND - 1))
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
echo "Error: No path specified." >&2
|
echo "Error: No path specified." >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TARGET_PATH="$1"
|
TARGET_PATH="$1"
|
||||||
|
|
||||||
# Set default CRF based on format if not specified
|
# Set default CRF based on format if not specified
|
||||||
if [[ -z $CRF ]]; then
|
if [[ -z $CRF ]]; then
|
||||||
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
||||||
CRF=23
|
CRF=23
|
||||||
else
|
else
|
||||||
CRF=30
|
CRF=30
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
ensure_ffmpeg
|
ensure_ffmpeg
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
if [[ ! -e $TARGET_PATH ]]; then
|
if [[ ! -e $TARGET_PATH ]]; then
|
||||||
echo "Error: Path '$TARGET_PATH' does not exist." >&2
|
echo "Error: Path '$TARGET_PATH' does not exist." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f $TARGET_PATH ]]; then
|
if [[ -f $TARGET_PATH ]]; then
|
||||||
# Single file
|
# Single file
|
||||||
if [[ ${TARGET_PATH,,} == *."$TARGET_FORMAT" ]]; then
|
if [[ ${TARGET_PATH,,} == *."$TARGET_FORMAT" ]]; then
|
||||||
log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping."
|
log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if is_video_file "$TARGET_PATH"; then
|
if is_video_file "$TARGET_PATH"; then
|
||||||
convert_video "$TARGET_PATH"
|
convert_video "$TARGET_PATH"
|
||||||
else
|
else
|
||||||
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
|
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif [[ -d $TARGET_PATH ]]; then
|
elif [[ -d $TARGET_PATH ]]; then
|
||||||
process_directory "$TARGET_PATH"
|
process_directory "$TARGET_PATH"
|
||||||
else
|
else
|
||||||
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
|
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Done!"
|
log "Done!"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -32,14 +32,14 @@ cd "$TRACKS_DIR"
|
|||||||
|
|
||||||
# Tracks to download (add/remove as needed)
|
# Tracks to download (add/remove as needed)
|
||||||
declare -A TRACKS=(
|
declare -A TRACKS=(
|
||||||
["python"]="https://github.com/exercism/python.git"
|
["python"]="https://github.com/exercism/python.git"
|
||||||
["c"]="https://github.com/exercism/c.git"
|
["c"]="https://github.com/exercism/c.git"
|
||||||
["cpp"]="https://github.com/exercism/cpp.git"
|
["cpp"]="https://github.com/exercism/cpp.git"
|
||||||
["javascript"]="https://github.com/exercism/javascript.git"
|
["javascript"]="https://github.com/exercism/javascript.git"
|
||||||
["typescript"]="https://github.com/exercism/typescript.git"
|
["typescript"]="https://github.com/exercism/typescript.git"
|
||||||
["rust"]="https://github.com/exercism/rust.git"
|
["rust"]="https://github.com/exercism/rust.git"
|
||||||
["go"]="https://github.com/exercism/go.git"
|
["go"]="https://github.com/exercism/go.git"
|
||||||
["bash"]="https://github.com/exercism/bash.git"
|
["bash"]="https://github.com/exercism/bash.git"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Optional tracks (uncomment to include)
|
# Optional tracks (uncomment to include)
|
||||||
@ -52,22 +52,22 @@ echo "Downloading ${#TRACKS[@]} tracks to: $TRACKS_DIR"
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
for track in "${!TRACKS[@]}"; do
|
for track in "${!TRACKS[@]}"; do
|
||||||
url="${TRACKS[$track]}"
|
url="${TRACKS[$track]}"
|
||||||
|
|
||||||
if [[ -d "$track" ]]; then
|
if [[ -d $track ]]; then
|
||||||
info "Updating $track..."
|
info "Updating $track..."
|
||||||
(cd "$track" && git pull --quiet) && success "$track updated"
|
(cd "$track" && git pull --quiet) && success "$track updated"
|
||||||
else
|
else
|
||||||
info "Cloning $track..."
|
info "Cloning $track..."
|
||||||
git clone --depth 1 "$url" && success "$track cloned"
|
git clone --depth 1 "$url" && success "$track cloned"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show exercise count
|
# Show exercise count
|
||||||
if [[ -d "$track/exercises/practice" ]]; then
|
if [[ -d "$track/exercises/practice" ]]; then
|
||||||
count=$(ls "$track/exercises/practice" | wc -l)
|
count=$(ls "$track/exercises/practice" | wc -l)
|
||||||
echo " → $count practice exercises available"
|
echo " → $count practice exercises available"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
@ -99,8 +99,8 @@ echo "=============================================="
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Track summary:"
|
echo "Track summary:"
|
||||||
for track in "${!TRACKS[@]}"; do
|
for track in "${!TRACKS[@]}"; do
|
||||||
if [[ -d "$track/exercises/practice" ]]; then
|
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"
|
printf " %-15s %3d exercises\n" "$track" "$count"
|
||||||
fi
|
fi
|
||||||
done | sort
|
done | sort
|
||||||
|
|||||||
@ -10,10 +10,10 @@ set -euo pipefail
|
|||||||
# Source common library if available
|
# Source common library if available
|
||||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
|
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
|
||||||
# shellcheck source=../lib/common.sh
|
# shellcheck source=../lib/common.sh
|
||||||
source "$SCRIPT_DIR/../lib/common.sh"
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
else
|
else
|
||||||
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@ -22,9 +22,9 @@ SEARCH_ROOT="/"
|
|||||||
TIMEOUT_SECONDS=30
|
TIMEOUT_SECONDS=30
|
||||||
|
|
||||||
# Ensure fd is installed
|
# 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"
|
log "ERROR: 'fd' is not installed. Install with: sudo pacman -S fd"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create destination directory if it doesn't exist
|
# Create destination directory if it doesn't exist
|
||||||
@ -41,31 +41,31 @@ log "Searching for .kdbx files across the system (timeout: ${TIMEOUT_SECONDS}s).
|
|||||||
# Use timeout to ensure the search doesn't take too long
|
# Use timeout to ensure the search doesn't take too long
|
||||||
# Exclude /proc, /sys, /dev, /run, /tmp, /var/cache, /var/tmp for speed
|
# Exclude /proc, /sys, /dev, /run, /tmp, /var/cache, /var/tmp for speed
|
||||||
FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \
|
FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \
|
||||||
-e kdbx \
|
-e kdbx \
|
||||||
-u \
|
-u \
|
||||||
-a \
|
-a \
|
||||||
--exclude '/proc' \
|
--exclude '/proc' \
|
||||||
--exclude '/sys' \
|
--exclude '/sys' \
|
||||||
--exclude '/dev' \
|
--exclude '/dev' \
|
||||||
--exclude '/run' \
|
--exclude '/run' \
|
||||||
--exclude '/tmp' \
|
--exclude '/tmp' \
|
||||||
--exclude '/var/cache' \
|
--exclude '/var/cache' \
|
||||||
--exclude '/var/tmp' \
|
--exclude '/var/tmp' \
|
||||||
--exclude '/snap' \
|
--exclude '/snap' \
|
||||||
--exclude '/.snapshots' \
|
--exclude '/.snapshots' \
|
||||||
--exclude '/lost+found' \
|
--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."
|
log "No .kdbx files found."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Count and display found files
|
# Count and display found files
|
||||||
FILE_COUNT=$(echo "$FOUND_FILES" | wc -l)
|
FILE_COUNT=$(echo "$FOUND_FILES" | wc -l)
|
||||||
log "Found $FILE_COUNT .kdbx file(s):"
|
log "Found $FILE_COUNT .kdbx file(s):"
|
||||||
echo "$FOUND_FILES" | while read -r file; do
|
echo "$FOUND_FILES" | while read -r file; do
|
||||||
echo " - $file"
|
echo " - $file"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Move files to destination
|
# Move files to destination
|
||||||
@ -74,51 +74,51 @@ MOVED_COUNT=0
|
|||||||
SKIPPED_COUNT=0
|
SKIPPED_COUNT=0
|
||||||
|
|
||||||
while IFS= read -r src_file; do
|
while IFS= read -r src_file; do
|
||||||
[[ -z "$src_file" ]] && continue
|
[[ -z $src_file ]] && continue
|
||||||
|
|
||||||
# Skip if file is already in destination
|
# Skip if file is already in destination
|
||||||
if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then
|
if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then
|
||||||
log "Skipping (already in destination): $src_file"
|
log "Skipping (already in destination): $src_file"
|
||||||
((SKIPPED_COUNT++)) || true
|
((SKIPPED_COUNT++)) || true
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get the base filename
|
# Get the base filename
|
||||||
base_name=$(basename "$src_file")
|
base_name=$(basename "$src_file")
|
||||||
dest_file="$DEST_DIR/$base_name"
|
dest_file="$DEST_DIR/$base_name"
|
||||||
|
|
||||||
# Handle filename conflicts by adding a number suffix
|
# 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)
|
# Check if it's the exact same file (by content)
|
||||||
if cmp -s "$src_file" "$dest_file"; then
|
if cmp -s "$src_file" "$dest_file"; then
|
||||||
log "Skipping (identical file exists): $src_file"
|
log "Skipping (identical file exists): $src_file"
|
||||||
# Remove the duplicate source file
|
# Remove the duplicate source file
|
||||||
rm -v "$src_file"
|
rm -v "$src_file"
|
||||||
((SKIPPED_COUNT++)) || true
|
((SKIPPED_COUNT++)) || true
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Different file with same name - add suffix
|
# Different file with same name - add suffix
|
||||||
counter=1
|
counter=1
|
||||||
name_without_ext="${base_name%.kdbx}"
|
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"
|
dest_file="$DEST_DIR/${name_without_ext} ($counter).kdbx"
|
||||||
((counter++))
|
((counter++))
|
||||||
done
|
done
|
||||||
log "Renaming to avoid conflict: $base_name -> $(basename "$dest_file")"
|
log "Renaming to avoid conflict: $base_name -> $(basename "$dest_file")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Move the file
|
# Move the file
|
||||||
if mv -v "$src_file" "$dest_file"; then
|
if mv -v "$src_file" "$dest_file"; then
|
||||||
((MOVED_COUNT++)) || true
|
((MOVED_COUNT++)) || true
|
||||||
else
|
else
|
||||||
log "ERROR: Failed to move $src_file"
|
log "ERROR: Failed to move $src_file"
|
||||||
fi
|
fi
|
||||||
done <<<"$FOUND_FILES"
|
done <<< "$FOUND_FILES"
|
||||||
|
|
||||||
log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)."
|
log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)."
|
||||||
log "All KeePassXC databases are now in: $DEST_DIR"
|
log "All KeePassXC databases are now in: $DEST_DIR"
|
||||||
|
|
||||||
# List final contents
|
# List final contents
|
||||||
log "Contents of $DEST_DIR:"
|
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"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ DEFAULT_RESOLUTION="320x240"
|
|||||||
|
|
||||||
# Function to display usage
|
# Function to display usage
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage: $0 <input_image> [resolution] [output_image]
|
Usage: $0 <input_image> [resolution] [output_image]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -31,7 +31,7 @@ Examples:
|
|||||||
|
|
||||||
Note: Requires ImageMagick (convert command)
|
Note: Requires ImageMagick (convert command)
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if ImageMagick is installed
|
# Check if ImageMagick is installed
|
||||||
@ -39,8 +39,8 @@ require_imagemagick "convert" || exit 1
|
|||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
echo "Error: Missing required argument <input_image>"
|
echo "Error: Missing required argument <input_image>"
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INPUT_IMAGE="$1"
|
INPUT_IMAGE="$1"
|
||||||
@ -49,20 +49,20 @@ OUTPUT_IMAGE="${3:-}"
|
|||||||
|
|
||||||
# Validate input image exists
|
# Validate input image exists
|
||||||
if [[ ! -f ${INPUT_IMAGE} ]]; then
|
if [[ ! -f ${INPUT_IMAGE} ]]; then
|
||||||
echo "Error: Input image '${INPUT_IMAGE}' does not exist."
|
echo "Error: Input image '${INPUT_IMAGE}' does not exist."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate resolution format (WIDTHxHEIGHT)
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
if ! validate_resolution "$RESOLUTION"; then
|
if ! validate_resolution "$RESOLUTION"; then
|
||||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate output filename if not provided
|
# Generate output filename if not provided
|
||||||
if [[ -z ${OUTPUT_IMAGE} ]]; then
|
if [[ -z ${OUTPUT_IMAGE} ]]; then
|
||||||
OUTPUT_IMAGE=$(generate_output_filename "${INPUT_IMAGE}" "_${RESOLUTION}")
|
OUTPUT_IMAGE=$(generate_output_filename "${INPUT_IMAGE}" "_${RESOLUTION}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Perform the conversion
|
# Perform the conversion
|
||||||
@ -70,15 +70,15 @@ echo "Converting '${INPUT_IMAGE}' to ${RESOLUTION}..."
|
|||||||
echo "Output will be saved to: ${OUTPUT_IMAGE}"
|
echo "Output will be saved to: ${OUTPUT_IMAGE}"
|
||||||
|
|
||||||
if convert "${INPUT_IMAGE}" -resize "${RESOLUTION}!" "${OUTPUT_IMAGE}"; then
|
if convert "${INPUT_IMAGE}" -resize "${RESOLUTION}!" "${OUTPUT_IMAGE}"; then
|
||||||
echo "✓ Successfully converted image to ${RESOLUTION}"
|
echo "✓ Successfully converted image to ${RESOLUTION}"
|
||||||
echo "Output: ${OUTPUT_IMAGE}"
|
echo "Output: ${OUTPUT_IMAGE}"
|
||||||
|
|
||||||
# Show file sizes
|
# Show file sizes
|
||||||
INPUT_SIZE=$(du -h "${INPUT_IMAGE}" | cut -f1)
|
INPUT_SIZE=$(du -h "${INPUT_IMAGE}" | cut -f1)
|
||||||
OUTPUT_SIZE=$(du -h "${OUTPUT_IMAGE}" | cut -f1)
|
OUTPUT_SIZE=$(du -h "${OUTPUT_IMAGE}" | cut -f1)
|
||||||
echo "Input size: ${INPUT_SIZE}"
|
echo "Input size: ${INPUT_SIZE}"
|
||||||
echo "Output size: ${OUTPUT_SIZE}"
|
echo "Output size: ${OUTPUT_SIZE}"
|
||||||
else
|
else
|
||||||
echo "✗ Error: Conversion failed"
|
echo "✗ Error: Conversion failed"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -34,286 +34,286 @@ echo ""
|
|||||||
|
|
||||||
# Install Exercism CLI
|
# Install Exercism CLI
|
||||||
install_exercism_cli() {
|
install_exercism_cli() {
|
||||||
if command -v exercism &>/dev/null; then
|
if command -v exercism &> /dev/null; then
|
||||||
local version
|
local version
|
||||||
version=$(exercism version 2>/dev/null | head -1)
|
version=$(exercism version 2> /dev/null | head -1)
|
||||||
success "Exercism CLI already installed: $version"
|
success "Exercism CLI already installed: $version"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Installing Exercism CLI..."
|
echo "Installing Exercism CLI..."
|
||||||
|
|
||||||
# Try package managers first
|
# Try package managers first
|
||||||
if command -v pacman &>/dev/null; then
|
if command -v pacman &> /dev/null; then
|
||||||
# Check AUR
|
# Check AUR
|
||||||
if command -v yay &>/dev/null; then
|
if command -v yay &> /dev/null; then
|
||||||
yay -S --noconfirm exercism-bin
|
yay -S --noconfirm exercism-bin
|
||||||
success "Exercism CLI installed via AUR"
|
success "Exercism CLI installed via AUR"
|
||||||
return 0
|
return 0
|
||||||
elif command -v paru &>/dev/null; then
|
elif command -v paru &> /dev/null; then
|
||||||
paru -S --noconfirm exercism-bin
|
paru -S --noconfirm exercism-bin
|
||||||
success "Exercism CLI installed via AUR"
|
success "Exercism CLI installed via AUR"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
elif command -v brew &>/dev/null; then
|
elif command -v brew &> /dev/null; then
|
||||||
brew install exercism
|
brew install exercism
|
||||||
success "Exercism CLI installed via Homebrew"
|
success "Exercism CLI installed via Homebrew"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Manual installation from GitHub releases
|
# Manual installation from GitHub releases
|
||||||
info "Installing from GitHub releases..."
|
info "Installing from GitHub releases..."
|
||||||
|
|
||||||
local arch
|
local arch
|
||||||
case "$(uname -m)" in
|
case "$(uname -m)" in
|
||||||
x86_64) arch="x86_64" ;;
|
x86_64) arch="x86_64" ;;
|
||||||
aarch64 | arm64) arch="arm64" ;;
|
aarch64 | arm64) arch="arm64" ;;
|
||||||
armv7l) arch="armv7" ;;
|
armv7l) arch="armv7" ;;
|
||||||
i686) arch="i386" ;;
|
i686) arch="i386" ;;
|
||||||
*)
|
*)
|
||||||
error "Unsupported architecture: $(uname -m)"
|
error "Unsupported architecture: $(uname -m)"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
local os="linux"
|
local os="linux"
|
||||||
[[ "$(uname -s)" == "Darwin" ]] && os="darwin"
|
[[ "$(uname -s)" == "Darwin" ]] && os="darwin"
|
||||||
|
|
||||||
# Get latest release
|
# Get latest release
|
||||||
local latest_url="https://api.github.com/repos/exercism/cli/releases/latest"
|
local latest_url="https://api.github.com/repos/exercism/cli/releases/latest"
|
||||||
local download_url
|
local download_url
|
||||||
|
|
||||||
download_url=$(curl -fsSL "$latest_url" | grep "browser_download_url.*${os}-${arch}" | head -1 | cut -d '"' -f 4)
|
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"
|
error "Could not find download URL for your system"
|
||||||
echo "Please install manually from: https://exercism.org/docs/using/solving-exercises/working-locally"
|
echo "Please install manually from: https://exercism.org/docs/using/solving-exercises/working-locally"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Downloading from: $download_url"
|
echo "Downloading from: $download_url"
|
||||||
local temp_dir
|
local temp_dir
|
||||||
temp_dir=$(mktemp -d)
|
temp_dir=$(mktemp -d)
|
||||||
|
|
||||||
curl -fL --progress-bar "$download_url" -o "$temp_dir/exercism.tar.gz"
|
curl -fL --progress-bar "$download_url" -o "$temp_dir/exercism.tar.gz"
|
||||||
tar -xzf "$temp_dir/exercism.tar.gz" -C "$temp_dir"
|
tar -xzf "$temp_dir/exercism.tar.gz" -C "$temp_dir"
|
||||||
|
|
||||||
# Install to ~/.local/bin
|
# Install to ~/.local/bin
|
||||||
mkdir -p "$HOME/.local/bin"
|
mkdir -p "$HOME/.local/bin"
|
||||||
mv "$temp_dir/exercism" "$HOME/.local/bin/"
|
mv "$temp_dir/exercism" "$HOME/.local/bin/"
|
||||||
chmod +x "$HOME/.local/bin/exercism"
|
chmod +x "$HOME/.local/bin/exercism"
|
||||||
|
|
||||||
rm -rf "$temp_dir"
|
rm -rf "$temp_dir"
|
||||||
|
|
||||||
success "Exercism CLI installed to ~/.local/bin/exercism"
|
success "Exercism CLI installed to ~/.local/bin/exercism"
|
||||||
|
|
||||||
# Check PATH
|
# Check PATH
|
||||||
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
||||||
warn "Add ~/.local/bin to your PATH:"
|
warn "Add ~/.local/bin to your PATH:"
|
||||||
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Configure exercism workspace
|
# Configure exercism workspace
|
||||||
configure_exercism() {
|
configure_exercism() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Configuring Exercism ==="
|
echo "=== Configuring Exercism ==="
|
||||||
|
|
||||||
mkdir -p "$EXERCISM_DIR"
|
mkdir -p "$EXERCISM_DIR"
|
||||||
|
|
||||||
# Check if already configured
|
# Check if already configured
|
||||||
if exercism configure 2>&1 | grep -q "workspace"; then
|
if exercism configure 2>&1 | grep -q "workspace"; then
|
||||||
success "Exercism already configured"
|
success "Exercism already configured"
|
||||||
else
|
else
|
||||||
# Set workspace directory
|
# Set workspace directory
|
||||||
exercism configure --workspace="$EXERCISM_DIR"
|
exercism configure --workspace="$EXERCISM_DIR"
|
||||||
success "Workspace set to: $EXERCISM_DIR"
|
success "Workspace set to: $EXERCISM_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "To fully configure Exercism with your account:"
|
info "To fully configure Exercism with your account:"
|
||||||
echo " 1. Create free account at https://exercism.org"
|
echo " 1. Create free account at https://exercism.org"
|
||||||
echo " 2. Go to https://exercism.org/settings/api_cli"
|
echo " 2. Go to https://exercism.org/settings/api_cli"
|
||||||
echo " 3. Copy your API token"
|
echo " 3. Copy your API token"
|
||||||
echo " 4. Run: exercism configure --token=YOUR_TOKEN"
|
echo " 4. Run: exercism configure --token=YOUR_TOKEN"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install test runners for languages
|
# Install test runners for languages
|
||||||
install_test_runners() {
|
install_test_runners() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Installing Test Runners ==="
|
echo "=== Installing Test Runners ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Python - pytest
|
# Python - pytest
|
||||||
if command -v python3 &>/dev/null; then
|
if command -v python3 &> /dev/null; then
|
||||||
if python3 -c "import pytest" 2>/dev/null; then
|
if python3 -c "import pytest" 2> /dev/null; then
|
||||||
success "Python: pytest already installed"
|
success "Python: pytest already installed"
|
||||||
else
|
else
|
||||||
info "Installing pytest for Python exercises..."
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# JavaScript/TypeScript - Node.js + npm
|
# 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))"
|
success "JavaScript/TypeScript: Node.js available ($(node --version))"
|
||||||
info " Tests run with: npm test (or jest)"
|
info " Tests run with: npm test (or jest)"
|
||||||
else
|
else
|
||||||
warn "JavaScript/TypeScript: Install Node.js for JS/TS exercises"
|
warn "JavaScript/TypeScript: Install Node.js for JS/TS exercises"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# C - gcc + criterion/cmocka
|
# C - gcc + criterion/cmocka
|
||||||
if command -v gcc &>/dev/null; then
|
if command -v gcc &> /dev/null; then
|
||||||
success "C: gcc available"
|
success "C: gcc available"
|
||||||
info " Some C exercises use Unity test framework (included in exercise)"
|
info " Some C exercises use Unity test framework (included in exercise)"
|
||||||
else
|
else
|
||||||
warn "C: Install gcc for C exercises"
|
warn "C: Install gcc for C exercises"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# C++ - g++ + Catch2/doctest
|
# C++ - g++ + Catch2/doctest
|
||||||
if command -v g++ &>/dev/null; then
|
if command -v g++ &> /dev/null; then
|
||||||
success "C++: g++ available"
|
success "C++: g++ available"
|
||||||
info " C++ exercises use Catch2 (header-only, included in exercise)"
|
info " C++ exercises use Catch2 (header-only, included in exercise)"
|
||||||
else
|
else
|
||||||
warn "C++: Install g++ for C++ exercises"
|
warn "C++: Install g++ for C++ exercises"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
if command -v cargo &>/dev/null; then
|
if command -v cargo &> /dev/null; then
|
||||||
success "Rust: cargo available (tests with: cargo test)"
|
success "Rust: cargo available (tests with: cargo test)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Go
|
# Go
|
||||||
if command -v go &>/dev/null; then
|
if command -v go &> /dev/null; then
|
||||||
success "Go: go available (tests with: go test)"
|
success "Go: go available (tests with: go test)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download exercises for a track (language)
|
# Download exercises for a track (language)
|
||||||
download_track() {
|
download_track() {
|
||||||
local track="$1"
|
local track="$1"
|
||||||
local count="${2:-10}"
|
local count="${2:-10}"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
info "Downloading $count exercises for $track track..."
|
info "Downloading $count exercises for $track track..."
|
||||||
|
|
||||||
# Get list of exercises
|
# Get list of exercises
|
||||||
local 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")
|
grep -oP '"slug":"\K[^"]+' | head -n "$count")
|
||||||
|
|
||||||
if [[ -z "$exercises" ]]; then
|
if [[ -z $exercises ]]; then
|
||||||
warn "Could not fetch exercise list for $track"
|
warn "Could not fetch exercise list for $track"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local downloaded=0
|
local downloaded=0
|
||||||
for exercise in $exercises; do
|
for exercise in $exercises; do
|
||||||
local exercise_dir="$EXERCISM_DIR/$track/$exercise"
|
local exercise_dir="$EXERCISM_DIR/$track/$exercise"
|
||||||
if [[ -d "$exercise_dir" ]]; then
|
if [[ -d $exercise_dir ]]; then
|
||||||
echo " [exists] $exercise"
|
echo " [exists] $exercise"
|
||||||
else
|
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"
|
echo " [downloaded] $exercise"
|
||||||
((downloaded++))
|
((downloaded++))
|
||||||
else
|
else
|
||||||
echo " [failed] $exercise (may require auth)"
|
echo " [failed] $exercise (may require auth)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
success "Downloaded $downloaded new exercises for $track"
|
success "Downloaded $downloaded new exercises for $track"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show available tracks and usage
|
# Show available tracks and usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo " Exercism Usage Guide"
|
echo " Exercism Usage Guide"
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}Download exercises:${NC}"
|
echo -e "${CYAN}Download exercises:${NC}"
|
||||||
echo " exercism download --track=python --exercise=hello-world"
|
echo " exercism download --track=python --exercise=hello-world"
|
||||||
echo " exercism download --track=javascript --exercise=two-fer"
|
echo " exercism download --track=javascript --exercise=two-fer"
|
||||||
echo " exercism download --track=c --exercise=isogram"
|
echo " exercism download --track=c --exercise=isogram"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}Run tests locally:${NC}"
|
echo -e "${CYAN}Run tests locally:${NC}"
|
||||||
echo " Python: cd ~/exercism/python/hello-world && pytest"
|
echo " Python: cd ~/exercism/python/hello-world && pytest"
|
||||||
echo " JavaScript: cd ~/exercism/javascript/hello-world && npm test"
|
echo " JavaScript: cd ~/exercism/javascript/hello-world && npm test"
|
||||||
echo " TypeScript: cd ~/exercism/typescript/hello-world && npm test"
|
echo " TypeScript: cd ~/exercism/typescript/hello-world && npm test"
|
||||||
echo " C: cd ~/exercism/c/hello-world && make test"
|
echo " C: cd ~/exercism/c/hello-world && make test"
|
||||||
echo " C++: cd ~/exercism/cpp/hello-world && make"
|
echo " C++: cd ~/exercism/cpp/hello-world && make"
|
||||||
echo " Rust: cd ~/exercism/rust/hello-world && cargo test"
|
echo " Rust: cd ~/exercism/rust/hello-world && cargo test"
|
||||||
echo " Go: cd ~/exercism/go/hello-world && go test"
|
echo " Go: cd ~/exercism/go/hello-world && go test"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}Submit solution (when online):${NC}"
|
echo -e "${CYAN}Submit solution (when online):${NC}"
|
||||||
echo " exercism submit solution.py"
|
echo " exercism submit solution.py"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}Popular tracks:${NC}"
|
echo -e "${CYAN}Popular tracks:${NC}"
|
||||||
echo " python, javascript, typescript, c, cpp, rust, go, java, ruby"
|
echo " python, javascript, typescript, c, cpp, rust, go, java, ruby"
|
||||||
echo " bash, elixir, haskell, kotlin, swift, csharp, php, sql"
|
echo " bash, elixir, haskell, kotlin, swift, csharp, php, sql"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}Batch download (requires API token):${NC}"
|
echo -e "${CYAN}Batch download (requires API token):${NC}"
|
||||||
echo " # Download first 20 Python exercises:"
|
echo " # Download first 20 Python exercises:"
|
||||||
echo " for ex in \$(exercism download --track=python 2>&1 | head -20); do"
|
echo ' for ex in $(exercism download --track=python 2>&1 | head -20); do'
|
||||||
echo " exercism download --track=python --exercise=\$ex"
|
echo ' exercism download --track=python --exercise=$ex'
|
||||||
echo " done"
|
echo " done"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Exercises are in: $EXERCISM_DIR"
|
echo "Exercises are in: $EXERCISM_DIR"
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
main() {
|
main() {
|
||||||
# Step 1: Install CLI
|
# Step 1: Install CLI
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 1: Installing Exercism CLI ==="
|
echo "=== Step 1: Installing Exercism CLI ==="
|
||||||
install_exercism_cli
|
install_exercism_cli
|
||||||
|
|
||||||
# Step 2: Configure
|
# Step 2: Configure
|
||||||
configure_exercism
|
configure_exercism
|
||||||
|
|
||||||
# Step 3: Install test runners
|
# Step 3: Install test runners
|
||||||
install_test_runners
|
install_test_runners
|
||||||
|
|
||||||
# Step 4: Download sample exercises
|
# Step 4: Download sample exercises
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 4: Downloading Sample Exercises ==="
|
echo "=== Step 4: Downloading Sample Exercises ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Downloading a few starter exercises for common languages..."
|
echo "Downloading a few starter exercises for common languages..."
|
||||||
echo "(Full download requires API token from exercism.org)"
|
echo "(Full download requires API token from exercism.org)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Try to download hello-world for each track
|
# Try to download hello-world for each track
|
||||||
local tracks=("python" "javascript" "typescript" "c" "cpp")
|
local tracks=("python" "javascript" "typescript" "c" "cpp")
|
||||||
|
|
||||||
for track in "${tracks[@]}"; do
|
for track in "${tracks[@]}"; do
|
||||||
local exercise_dir="$EXERCISM_DIR/$track/hello-world"
|
local exercise_dir="$EXERCISM_DIR/$track/hello-world"
|
||||||
if [[ -d "$exercise_dir" ]]; then
|
if [[ -d $exercise_dir ]]; then
|
||||||
echo " [$track] hello-world already exists"
|
echo " [$track] hello-world already exists"
|
||||||
else
|
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"
|
success "[$track] hello-world downloaded"
|
||||||
else
|
else
|
||||||
warn "[$track] hello-world requires authentication"
|
warn "[$track] hello-world requires authentication"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Show usage
|
# Show usage
|
||||||
show_usage
|
show_usage
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
success "Installation complete!"
|
success "Installation complete!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
echo " 1. Sign up at https://exercism.org (free)"
|
echo " 1. Sign up at https://exercism.org (free)"
|
||||||
echo " 2. Get your token from https://exercism.org/settings/api_cli"
|
echo " 2. Get your token from https://exercism.org/settings/api_cli"
|
||||||
echo " 3. Run: exercism configure --token=YOUR_TOKEN"
|
echo " 3. Run: exercism configure --token=YOUR_TOKEN"
|
||||||
echo " 4. Download exercises and code offline!"
|
echo " 4. Download exercises and code offline!"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -27,202 +27,202 @@ echo ""
|
|||||||
|
|
||||||
# Detect package manager and install Zeal
|
# Detect package manager and install Zeal
|
||||||
install_zeal() {
|
install_zeal() {
|
||||||
if command -v zeal &>/dev/null; then
|
if command -v zeal &> /dev/null; then
|
||||||
success "Zeal is already installed"
|
success "Zeal is already installed"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Installing Zeal offline documentation browser..."
|
echo "Installing Zeal offline documentation browser..."
|
||||||
|
|
||||||
if command -v pacman &>/dev/null; then
|
if command -v pacman &> /dev/null; then
|
||||||
# Arch Linux
|
# Arch Linux
|
||||||
sudo pacman -S --noconfirm zeal
|
sudo pacman -S --noconfirm zeal
|
||||||
elif command -v apt &>/dev/null; then
|
elif command -v apt &> /dev/null; then
|
||||||
# Debian/Ubuntu
|
# Debian/Ubuntu
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y zeal
|
sudo apt install -y zeal
|
||||||
elif command -v dnf &>/dev/null; then
|
elif command -v dnf &> /dev/null; then
|
||||||
# Fedora
|
# Fedora
|
||||||
sudo dnf install -y zeal
|
sudo dnf install -y zeal
|
||||||
elif command -v zypper &>/dev/null; then
|
elif command -v zypper &> /dev/null; then
|
||||||
# openSUSE
|
# openSUSE
|
||||||
sudo zypper install -y zeal
|
sudo zypper install -y zeal
|
||||||
elif command -v flatpak &>/dev/null; then
|
elif command -v flatpak &> /dev/null; then
|
||||||
# Flatpak fallback
|
# Flatpak fallback
|
||||||
flatpak install -y flathub org.zealdocs.Zeal
|
flatpak install -y flathub org.zealdocs.Zeal
|
||||||
else
|
else
|
||||||
error "Could not detect package manager. Please install Zeal manually:"
|
error "Could not detect package manager. Please install Zeal manually:"
|
||||||
echo " https://zealdocs.org/download.html"
|
echo " https://zealdocs.org/download.html"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
success "Zeal installed successfully"
|
success "Zeal installed successfully"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get Zeal docsets directory
|
# Get Zeal docsets directory
|
||||||
get_docsets_dir() {
|
get_docsets_dir() {
|
||||||
local docsets_dir
|
local docsets_dir
|
||||||
|
|
||||||
# Check if using Flatpak
|
# 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"
|
docsets_dir="$HOME/.var/app/org.zealdocs.Zeal/data/Zeal/Zeal/docsets"
|
||||||
else
|
else
|
||||||
# Standard installation
|
# Standard installation
|
||||||
docsets_dir="$HOME/.local/share/Zeal/Zeal/docsets"
|
docsets_dir="$HOME/.local/share/Zeal/Zeal/docsets"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$docsets_dir"
|
mkdir -p "$docsets_dir"
|
||||||
echo "$docsets_dir"
|
echo "$docsets_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Download a docset from Zeal feeds
|
# Download a docset from Zeal feeds
|
||||||
download_docset() {
|
download_docset() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
local docsets_dir="$2"
|
local docsets_dir="$2"
|
||||||
|
|
||||||
# Check if already installed
|
# Check if already installed
|
||||||
if [ -d "$docsets_dir/${name}.docset" ]; then
|
if [ -d "$docsets_dir/${name}.docset" ]; then
|
||||||
warn "$name docset already installed"
|
warn "$name docset already installed"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Downloading $name documentation..."
|
info "Downloading $name documentation..."
|
||||||
|
|
||||||
# Use Zeal's built-in feed system via CLI or direct download
|
# Use Zeal's built-in feed system via CLI or direct download
|
||||||
# Zeal stores docsets in .docset directories
|
# Zeal stores docsets in .docset directories
|
||||||
|
|
||||||
# Try to get from dash-user-contributions or official feeds
|
# Try to get from dash-user-contributions or official feeds
|
||||||
local download_url=""
|
local download_url=""
|
||||||
|
|
||||||
case "$name" in
|
case "$name" in
|
||||||
"C")
|
"C")
|
||||||
download_url="http://kapeli.com/feeds/C.tgz"
|
download_url="http://kapeli.com/feeds/C.tgz"
|
||||||
;;
|
;;
|
||||||
"C++")
|
"C++")
|
||||||
download_url="http://kapeli.com/feeds/C%2B%2B.tgz"
|
download_url="http://kapeli.com/feeds/C%2B%2B.tgz"
|
||||||
;;
|
;;
|
||||||
"JavaScript")
|
"JavaScript")
|
||||||
download_url="http://kapeli.com/feeds/JavaScript.tgz"
|
download_url="http://kapeli.com/feeds/JavaScript.tgz"
|
||||||
;;
|
;;
|
||||||
"TypeScript")
|
"TypeScript")
|
||||||
download_url="http://kapeli.com/feeds/TypeScript.tgz"
|
download_url="http://kapeli.com/feeds/TypeScript.tgz"
|
||||||
;;
|
;;
|
||||||
"Python_3")
|
"Python_3")
|
||||||
download_url="http://kapeli.com/feeds/Python_3.tgz"
|
download_url="http://kapeli.com/feeds/Python_3.tgz"
|
||||||
;;
|
;;
|
||||||
"Python_2")
|
"Python_2")
|
||||||
download_url="http://kapeli.com/feeds/Python_2.tgz"
|
download_url="http://kapeli.com/feeds/Python_2.tgz"
|
||||||
;;
|
;;
|
||||||
"Bash")
|
"Bash")
|
||||||
download_url="http://kapeli.com/feeds/Bash.tgz"
|
download_url="http://kapeli.com/feeds/Bash.tgz"
|
||||||
;;
|
;;
|
||||||
"HTML")
|
"HTML")
|
||||||
download_url="http://kapeli.com/feeds/HTML.tgz"
|
download_url="http://kapeli.com/feeds/HTML.tgz"
|
||||||
;;
|
;;
|
||||||
"CSS")
|
"CSS")
|
||||||
download_url="http://kapeli.com/feeds/CSS.tgz"
|
download_url="http://kapeli.com/feeds/CSS.tgz"
|
||||||
;;
|
;;
|
||||||
"NodeJS")
|
"NodeJS")
|
||||||
download_url="http://kapeli.com/feeds/NodeJS.tgz"
|
download_url="http://kapeli.com/feeds/NodeJS.tgz"
|
||||||
;;
|
;;
|
||||||
"React")
|
"React")
|
||||||
download_url="http://kapeli.com/feeds/React.tgz"
|
download_url="http://kapeli.com/feeds/React.tgz"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
warn "Unknown docset: $name"
|
warn "Unknown docset: $name"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Download and extract
|
# Download and extract
|
||||||
local temp_file
|
local temp_file
|
||||||
temp_file=$(mktemp)
|
temp_file=$(mktemp)
|
||||||
|
|
||||||
echo " URL: $download_url"
|
echo " URL: $download_url"
|
||||||
if curl -fL --progress-bar "$download_url" -o "$temp_file"; then
|
if curl -fL --progress-bar "$download_url" -o "$temp_file"; then
|
||||||
echo " Extracting to $docsets_dir..."
|
echo " Extracting to $docsets_dir..."
|
||||||
tar -xzf "$temp_file" -C "$docsets_dir"
|
tar -xzf "$temp_file" -C "$docsets_dir"
|
||||||
rm -f "$temp_file"
|
rm -f "$temp_file"
|
||||||
success "$name documentation downloaded"
|
success "$name documentation downloaded"
|
||||||
else
|
else
|
||||||
rm -f "$temp_file"
|
rm -f "$temp_file"
|
||||||
warn "Failed to download $name - you can install it from Zeal's UI"
|
warn "Failed to download $name - you can install it from Zeal's UI"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main installation
|
# Main installation
|
||||||
main() {
|
main() {
|
||||||
# Step 1: Install Zeal
|
# Step 1: Install Zeal
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 1: Installing Zeal ==="
|
echo "=== Step 1: Installing Zeal ==="
|
||||||
install_zeal || exit 1
|
install_zeal || exit 1
|
||||||
|
|
||||||
# Step 2: Get docsets directory
|
# Step 2: Get docsets directory
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 2: Preparing docsets directory ==="
|
echo "=== Step 2: Preparing docsets directory ==="
|
||||||
local docsets_dir
|
local docsets_dir
|
||||||
docsets_dir=$(get_docsets_dir)
|
docsets_dir=$(get_docsets_dir)
|
||||||
success "Docsets directory: $docsets_dir"
|
success "Docsets directory: $docsets_dir"
|
||||||
|
|
||||||
# Step 3: Download requested docsets
|
# Step 3: Download requested docsets
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 3: Downloading Documentation ==="
|
echo "=== Step 3: Downloading Documentation ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Core requested languages
|
# Core requested languages
|
||||||
local docsets=("C" "C++" "JavaScript" "TypeScript" "Python_3")
|
local docsets=("C" "C++" "JavaScript" "TypeScript" "Python_3")
|
||||||
|
|
||||||
# Optional extras (comment out if not needed)
|
# Optional extras (comment out if not needed)
|
||||||
local extras=("Bash" "HTML" "CSS" "NodeJS")
|
local extras=("Bash" "HTML" "CSS" "NodeJS")
|
||||||
|
|
||||||
# Download core docsets
|
# Download core docsets
|
||||||
for docset in "${docsets[@]}"; do
|
for docset in "${docsets[@]}"; do
|
||||||
download_docset "$docset" "$docsets_dir"
|
download_docset "$docset" "$docsets_dir"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Ask about extras
|
# Ask about extras
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p "Install additional docsets (Bash, HTML, CSS, NodeJS)? [Y/n] " response
|
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
|
for docset in "${extras[@]}"; do
|
||||||
download_docset "$docset" "$docsets_dir"
|
download_docset "$docset" "$docsets_dir"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo " Installation Complete!"
|
echo " Installation Complete!"
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Installed documentation:"
|
echo "Installed documentation:"
|
||||||
for f in "$docsets_dir"/*.docset; do
|
for f in "$docsets_dir"/*.docset; do
|
||||||
if [[ -d "$f" ]]; then
|
if [[ -d $f ]]; then
|
||||||
echo " ✓ $(basename "$f" .docset)"
|
echo " ✓ $(basename "$f" .docset)"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo " Launch Zeal from your application menu, or run: zeal"
|
echo " Launch Zeal from your application menu, or run: zeal"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To download additional docsets:"
|
echo "To download additional docsets:"
|
||||||
echo " 1. Open Zeal"
|
echo " 1. Open Zeal"
|
||||||
echo " 2. Go to Tools → Docsets"
|
echo " 2. Go to Tools → Docsets"
|
||||||
echo " 3. Click 'Available' tab and download what you need"
|
echo " 3. Click 'Available' tab and download what you need"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Keyboard shortcut tip:"
|
echo "Keyboard shortcut tip:"
|
||||||
echo " Set a global hotkey in Zeal → Preferences → Global Shortcuts"
|
echo " Set a global hotkey in Zeal → Preferences → Global Shortcuts"
|
||||||
echo " (e.g., Alt+Space for quick documentation lookup)"
|
echo " (e.g., Alt+Space for quick documentation lookup)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
|
|
||||||
# Offer to launch Zeal
|
# Offer to launch Zeal
|
||||||
read -r -p "Launch Zeal now? [y/N] " response
|
read -r -p "Launch Zeal now? [y/N] " response
|
||||||
if [[ "$response" =~ ^[Yy]$ ]]; then
|
if [[ $response =~ ^[Yy]$ ]]; then
|
||||||
nohup zeal &>/dev/null &
|
nohup zeal &> /dev/null &
|
||||||
success "Zeal launched"
|
success "Zeal launched"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -39,18 +39,18 @@ echo ""
|
|||||||
echo "=== 1. Installing Python NLP-based Plagiarism Tools ==="
|
echo "=== 1. Installing Python NLP-based Plagiarism Tools ==="
|
||||||
|
|
||||||
# Check for Python 3
|
# 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."
|
error "Python 3 is required but not installed."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
if [ ! -d "$VENV_DIR" ]; then
|
if [ ! -d "$VENV_DIR" ]; then
|
||||||
echo "Creating Python virtual environment..."
|
echo "Creating Python virtual environment..."
|
||||||
python3 -m venv "$VENV_DIR"
|
python3 -m venv "$VENV_DIR"
|
||||||
success "Virtual environment created at $VENV_DIR"
|
success "Virtual environment created at $VENV_DIR"
|
||||||
else
|
else
|
||||||
warn "Virtual environment already exists at $VENV_DIR"
|
warn "Virtual environment already exists at $VENV_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Activate and install packages
|
# Activate and install packages
|
||||||
@ -60,19 +60,19 @@ echo "Installing Python packages for text similarity detection..."
|
|||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
|
|
||||||
pip install --progress-bar on \
|
pip install --progress-bar on \
|
||||||
scikit-learn \
|
scikit-learn \
|
||||||
nltk \
|
nltk \
|
||||||
spacy \
|
spacy \
|
||||||
gensim \
|
gensim \
|
||||||
numpy \
|
numpy \
|
||||||
pandas \
|
pandas \
|
||||||
python-docx \
|
python-docx \
|
||||||
PyPDF2 \
|
PyPDF2 \
|
||||||
beautifulsoup4 \
|
beautifulsoup4 \
|
||||||
lxml \
|
lxml \
|
||||||
textdistance \
|
textdistance \
|
||||||
fuzzywuzzy \
|
fuzzywuzzy \
|
||||||
python-Levenshtein
|
python-Levenshtein
|
||||||
|
|
||||||
success "Python NLP packages installed"
|
success "Python NLP packages installed"
|
||||||
|
|
||||||
@ -90,11 +90,11 @@ success "NLTK data downloaded"
|
|||||||
|
|
||||||
# Download spaCy English model (small)
|
# Download spaCy English model (small)
|
||||||
echo "Downloading spaCy English model..."
|
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"
|
success "spaCy model installed"
|
||||||
|
|
||||||
# Create a simple plagiarism checker script
|
# Create a simple plagiarism checker script
|
||||||
cat >"$INSTALL_DIR/check_plagiarism.py" <<'PYEOF'
|
cat > "$INSTALL_DIR/check_plagiarism.py" << 'PYEOF'
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Simple Text Plagiarism Checker
|
Simple Text Plagiarism Checker
|
||||||
@ -325,7 +325,7 @@ success "Created plagiarism checker script at $INSTALL_DIR/check_plagiarism.py"
|
|||||||
|
|
||||||
# Create convenience wrapper
|
# Create convenience wrapper
|
||||||
mkdir -p "$HOME/.local/bin"
|
mkdir -p "$HOME/.local/bin"
|
||||||
cat >"$HOME/.local/bin/plagcheck" <<WRAPEOF
|
cat > "$HOME/.local/bin/plagcheck" << WRAPEOF
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Wrapper for plagiarism checker
|
# Wrapper for plagiarism checker
|
||||||
source "$VENV_DIR/bin/activate"
|
source "$VENV_DIR/bin/activate"
|
||||||
@ -344,14 +344,14 @@ echo "=== 2. Installing Sherlock Text Plagiarism Detector ==="
|
|||||||
|
|
||||||
SHERLOCK_DIR="$INSTALL_DIR/sherlock"
|
SHERLOCK_DIR="$INSTALL_DIR/sherlock"
|
||||||
if [ ! -d "$SHERLOCK_DIR" ]; then
|
if [ ! -d "$SHERLOCK_DIR" ]; then
|
||||||
# There are several Sherlock implementations; using a popular Python one
|
# 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
|
# 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..."
|
warn "Could not clone sherlock-py, trying alternative..."
|
||||||
# Alternative: Create a simple n-gram based sherlock
|
# Alternative: Create a simple n-gram based sherlock
|
||||||
mkdir -p "$SHERLOCK_DIR"
|
mkdir -p "$SHERLOCK_DIR"
|
||||||
cat >"$SHERLOCK_DIR/sherlock.py" <<'SHERLOCKEOF'
|
cat > "$SHERLOCK_DIR/sherlock.py" << 'SHERLOCKEOF'
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Sherlock - Simple text plagiarism detector using n-gram fingerprinting.
|
Sherlock - Simple text plagiarism detector using n-gram fingerprinting.
|
||||||
@ -443,14 +443,14 @@ def main():
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
SHERLOCKEOF
|
SHERLOCKEOF
|
||||||
chmod +x "$SHERLOCK_DIR/sherlock.py"
|
chmod +x "$SHERLOCK_DIR/sherlock.py"
|
||||||
}
|
}
|
||||||
success "Sherlock installed at $SHERLOCK_DIR"
|
success "Sherlock installed at $SHERLOCK_DIR"
|
||||||
else
|
else
|
||||||
warn "Git not available, skipping Sherlock installation"
|
warn "Git not available, skipping Sherlock installation"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
warn "Sherlock already installed at $SHERLOCK_DIR"
|
warn "Sherlock already installed at $SHERLOCK_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -459,17 +459,17 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== 3. Checking for Ferret (Java-based plagiarism tool) ==="
|
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"
|
FERRET_DIR="$INSTALL_DIR/ferret"
|
||||||
if [ ! -d "$FERRET_DIR" ]; then
|
if [ ! -d "$FERRET_DIR" ]; then
|
||||||
mkdir -p "$FERRET_DIR"
|
mkdir -p "$FERRET_DIR"
|
||||||
echo "Ferret is a Java-based tool from University of Hertfordshire."
|
echo "Ferret is a Java-based tool from University of Hertfordshire."
|
||||||
echo "Download manually from: https://homepages.herts.ac.uk/~comqcln/Ferret/"
|
echo "Download manually from: https://homepages.herts.ac.uk/~comqcln/Ferret/"
|
||||||
echo "Place JAR file in: $FERRET_DIR"
|
echo "Place JAR file in: $FERRET_DIR"
|
||||||
warn "Ferret requires manual download (academic license)"
|
warn "Ferret requires manual download (academic license)"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
warn "Java not installed, skipping Ferret"
|
warn "Java not installed, skipping Ferret"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -478,16 +478,16 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
echo "=== 4. WCopyfind Information (Windows tool, needs Wine) ==="
|
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 "Wine is available. WCopyfind can be run via Wine."
|
||||||
echo "Download from: https://plagiarism.bloomfieldmedia.com/software/wcopyfind/"
|
echo "Download from: https://plagiarism.bloomfieldmedia.com/software/wcopyfind/"
|
||||||
echo "Run with: wine /path/to/WCopyfind.exe"
|
echo "Run with: wine /path/to/WCopyfind.exe"
|
||||||
warn "WCopyfind requires manual download"
|
warn "WCopyfind requires manual download"
|
||||||
else
|
else
|
||||||
echo "Wine not installed. To use WCopyfind:"
|
echo "Wine not installed. To use WCopyfind:"
|
||||||
echo " 1. Install wine: sudo apt install wine (or equivalent)"
|
echo " 1. Install wine: sudo apt install wine (or equivalent)"
|
||||||
echo " 2. Download WCopyfind from: https://plagiarism.bloomfieldmedia.com/software/wcopyfind/"
|
echo " 2. Download WCopyfind from: https://plagiarism.bloomfieldmedia.com/software/wcopyfind/"
|
||||||
warn "WCopyfind skipped (Wine not available)"
|
warn "WCopyfind skipped (Wine not available)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -527,6 +527,6 @@ echo "=============================================="
|
|||||||
|
|
||||||
# Add to PATH reminder
|
# Add to PATH reminder
|
||||||
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
||||||
warn "Add ~/.local/bin to your PATH by adding this to ~/.bashrc or ~/.zshrc:"
|
warn "Add ~/.local/bin to your PATH by adding this to ~/.bashrc or ~/.zshrc:"
|
||||||
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
echo ' export PATH="$HOME/.local/bin:$PATH"'
|
||||||
fi
|
fi
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ OUTPUT_FORMAT="jpg"
|
|||||||
PDF_FILES=()
|
PDF_FILES=()
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage:
|
Usage:
|
||||||
$(basename "$0") [OPTIONS] PDF_FILE [PDF_FILE...]
|
$(basename "$0") [OPTIONS] PDF_FILE [PDF_FILE...]
|
||||||
|
|
||||||
@ -36,80 +36,80 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_magick() {
|
ensure_magick() {
|
||||||
require_imagemagick "magick" || exit 1
|
require_imagemagick "magick" || exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
local opt
|
local opt
|
||||||
OUTPUT_DIR=""
|
OUTPUT_DIR=""
|
||||||
OUTPUT_FORMAT="jpg"
|
OUTPUT_FORMAT="jpg"
|
||||||
PDF_FILES=()
|
PDF_FILES=()
|
||||||
|
|
||||||
while getopts ":o:f:h" opt; do
|
while getopts ":o:f:h" opt; do
|
||||||
case "$opt" in
|
case "$opt" in
|
||||||
o)
|
o)
|
||||||
OUTPUT_DIR="$OPTARG"
|
OUTPUT_DIR="$OPTARG"
|
||||||
;;
|
;;
|
||||||
f)
|
f)
|
||||||
OUTPUT_FORMAT="$OPTARG"
|
OUTPUT_FORMAT="$OPTARG"
|
||||||
;;
|
;;
|
||||||
h)
|
h)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
shift $((OPTIND - 1))
|
shift $((OPTIND - 1))
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
echo "Error: at least one PDF file must be specified." >&2
|
echo "Error: at least one PDF file must be specified." >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PDF_FILES=("$@")
|
PDF_FILES=("$@")
|
||||||
|
|
||||||
if [[ -z ${OUTPUT_DIR:-} ]]; then
|
if [[ -z ${OUTPUT_DIR:-} ]]; then
|
||||||
OUTPUT_DIR="${PWD}"
|
OUTPUT_DIR="${PWD}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -d $OUTPUT_DIR ]]; then
|
if [[ ! -d $OUTPUT_DIR ]]; then
|
||||||
mkdir -p "$OUTPUT_DIR"
|
mkdir -p "$OUTPUT_DIR"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
convert_pdf() {
|
convert_pdf() {
|
||||||
local pdf_file="$1"
|
local pdf_file="$1"
|
||||||
local base name out_pattern
|
local base name out_pattern
|
||||||
|
|
||||||
name="$(basename "$pdf_file")"
|
name="$(basename "$pdf_file")"
|
||||||
base="${name%.*}"
|
base="${name%.*}"
|
||||||
out_pattern="${OUTPUT_DIR%/}/${base}_page-"
|
out_pattern="${OUTPUT_DIR%/}/${base}_page-"
|
||||||
|
|
||||||
log "Converting '$pdf_file' to $OUTPUT_FORMAT using magick -> ${out_pattern}*.${OUTPUT_FORMAT}"
|
log "Converting '$pdf_file' to $OUTPUT_FORMAT using magick -> ${out_pattern}*.${OUTPUT_FORMAT}"
|
||||||
magick -density 300 "$pdf_file" -quality 90 "${out_pattern}%d.${OUTPUT_FORMAT}"
|
magick -density 300 "$pdf_file" -quality 90 "${out_pattern}%d.${OUTPUT_FORMAT}"
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
ensure_magick
|
ensure_magick
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
local pdf
|
local pdf
|
||||||
for pdf in "${PDF_FILES[@]}"; do
|
for pdf in "${PDF_FILES[@]}"; do
|
||||||
if [[ ! -f $pdf ]]; then
|
if [[ ! -f $pdf ]]; then
|
||||||
echo "Warning: '$pdf' is not a regular file, skipping." >&2
|
echo "Warning: '$pdf' is not a regular file, skipping." >&2
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
convert_pdf "$pdf"
|
convert_pdf "$pdf"
|
||||||
done
|
done
|
||||||
|
|
||||||
log "Done converting PDFs to ${OUTPUT_FORMAT}. Output directory: $OUTPUT_DIR"
|
log "Done converting PDFs to ${OUTPUT_FORMAT}. Output directory: $OUTPUT_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -45,37 +45,37 @@ NC='\033[0m'
|
|||||||
# Helper Functions (all print to stderr to not interfere with return values)
|
# Helper Functions (all print to stderr to not interfere with return values)
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
print_header() {
|
print_header() {
|
||||||
echo -e "\n${BOLD}${CYAN}════════════════════════════════════════════════════════════${NC}" >&2
|
echo -e "\n${BOLD}${CYAN}════════════════════════════════════════════════════════════${NC}" >&2
|
||||||
echo -e "${BOLD}${CYAN} $1${NC}" >&2
|
echo -e "${BOLD}${CYAN} $1${NC}" >&2
|
||||||
echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════${NC}\n" >&2
|
echo -e "${BOLD}${CYAN}════════════════════════════════════════════════════════════${NC}\n" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_step() {
|
print_step() {
|
||||||
echo -e "${BOLD}${BLUE}▶ $1${NC}" >&2
|
echo -e "${BOLD}${BLUE}▶ $1${NC}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_success() {
|
print_success() {
|
||||||
echo -e "${GREEN}✓ $1${NC}" >&2
|
echo -e "${GREEN}✓ $1${NC}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_error() {
|
print_error() {
|
||||||
echo -e "${RED}✗ $1${NC}" >&2
|
echo -e "${RED}✗ $1${NC}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
print_info() {
|
print_info() {
|
||||||
echo -e "${YELLOW}→ $1${NC}" >&2
|
echo -e "${YELLOW}→ $1${NC}" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
if [ -d "$WORK_DIR" ] && [ "$WORK_DIR" != "/" ]; then
|
if [ -d "$WORK_DIR" ] && [ "$WORK_DIR" != "/" ]; then
|
||||||
rm -rf "$WORK_DIR"
|
rm -rf "$WORK_DIR"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
repo_to_study.sh - Generate study materials from any repository
|
repo_to_study.sh - Generate study materials from any repository
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
@ -99,54 +99,54 @@ OUTPUT FILES:
|
|||||||
analysis/ - Raw analysis data (imports, keywords, functions)
|
analysis/ - Raw analysis data (imports, keywords, functions)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Check Dependencies
|
# Check Dependencies
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
check_dependencies() {
|
check_dependencies() {
|
||||||
local missing=()
|
local missing=()
|
||||||
|
|
||||||
# Check for required scripts
|
# Check for required scripts
|
||||||
if [ ! -x "$ANALYZE_SCRIPT" ]; then
|
if [ ! -x "$ANALYZE_SCRIPT" ]; then
|
||||||
missing+=("analyze_repo.sh not found at $ANALYZE_SCRIPT")
|
missing+=("analyze_repo.sh not found at $ANALYZE_SCRIPT")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -x "$STUDY_SCRIPT" ]; then
|
||||||
|
missing+=("generate_study_materials.sh not found at $STUDY_SCRIPT")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for basic tools
|
||||||
|
for cmd in git curl grep sed awk; do
|
||||||
|
if ! command -v "$cmd" &> /dev/null; then
|
||||||
|
missing+=("$cmd")
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
if [ ! -x "$STUDY_SCRIPT" ]; then
|
|
||||||
missing+=("generate_study_materials.sh not found at $STUDY_SCRIPT")
|
if [ ${#missing[@]} -gt 0 ]; then
|
||||||
fi
|
print_error "Missing dependencies:"
|
||||||
|
for dep in "${missing[@]}"; do
|
||||||
# Check for basic tools
|
echo " - $dep"
|
||||||
for cmd in git curl grep sed awk; do
|
|
||||||
if ! command -v "$cmd" &>/dev/null; then
|
|
||||||
missing+=("$cmd")
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
exit 1
|
||||||
if [ ${#missing[@]} -gt 0 ]; then
|
fi
|
||||||
print_error "Missing dependencies:"
|
|
||||||
for dep in "${missing[@]}"; do
|
|
||||||
echo " - $dep"
|
|
||||||
done
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Ensure Offline Docs are Available
|
# Ensure Offline Docs are Available
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
ensure_offline_docs() {
|
ensure_offline_docs() {
|
||||||
local docs_dir="$HOME/.local/share/offline-docs"
|
local docs_dir="$HOME/.local/share/offline-docs"
|
||||||
|
|
||||||
if [ ! -d "$docs_dir/python" ]; then
|
if [ ! -d "$docs_dir/python" ]; then
|
||||||
print_info "Offline docs not found. Setting up Python documentation..."
|
print_info "Offline docs not found. Setting up Python documentation..."
|
||||||
if [ -x "$SETUP_DOCS_SCRIPT" ]; then
|
if [ -x "$SETUP_DOCS_SCRIPT" ]; then
|
||||||
"$SETUP_DOCS_SCRIPT" --python
|
"$SETUP_DOCS_SCRIPT" --python
|
||||||
else
|
else
|
||||||
print_info "Run setup_offline_docs.sh --all to enable offline documentation"
|
print_info "Run setup_offline_docs.sh --all to enable offline documentation"
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Global to store repo name for cloned repos
|
# Global to store repo name for cloned repos
|
||||||
@ -156,209 +156,209 @@ REPO_NAME=""
|
|||||||
# Get Repository
|
# Get Repository
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
get_repo() {
|
get_repo() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
local repo_dir=""
|
local repo_dir=""
|
||||||
|
|
||||||
# Check if it's a URL (git clone needed)
|
# 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..."
|
print_step "Cloning repository..."
|
||||||
|
|
||||||
# Extract repo name from URL
|
# Extract repo name from URL
|
||||||
REPO_NAME=$(basename "$input" .git)
|
REPO_NAME=$(basename "$input" .git)
|
||||||
repo_dir="$WORK_DIR/$REPO_NAME"
|
repo_dir="$WORK_DIR/$REPO_NAME"
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$WORK_DIR"
|
||||||
|
|
||||||
if git clone --depth 1 "$input" "$repo_dir" >&2 2>&1; then
|
if git clone --depth 1 "$input" "$repo_dir" >&2 2>&1; then
|
||||||
print_success "Cloned: $input"
|
print_success "Cloned: $input"
|
||||||
else
|
|
||||||
print_error "Failed to clone repository"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$repo_dir"
|
|
||||||
# Local path
|
|
||||||
elif [ -d "$input" ]; then
|
|
||||||
# Convert to absolute path
|
|
||||||
repo_dir="$(cd "$input" && pwd)"
|
|
||||||
REPO_NAME=$(basename "$repo_dir")
|
|
||||||
print_success "Using local repository: $repo_dir"
|
|
||||||
echo "$repo_dir"
|
|
||||||
else
|
else
|
||||||
print_error "Invalid input: '$input' is not a valid URL or directory"
|
print_error "Failed to clone repository"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "$repo_dir"
|
||||||
|
# Local path
|
||||||
|
elif [ -d "$input" ]; then
|
||||||
|
# Convert to absolute path
|
||||||
|
repo_dir="$(cd "$input" && pwd)"
|
||||||
|
REPO_NAME=$(basename "$repo_dir")
|
||||||
|
print_success "Using local repository: $repo_dir"
|
||||||
|
echo "$repo_dir"
|
||||||
|
else
|
||||||
|
print_error "Invalid input: '$input' is not a valid URL or directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Analyze Repository
|
# Analyze Repository
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
analyze_repo() {
|
analyze_repo() {
|
||||||
local repo_path="$1"
|
local repo_path="$1"
|
||||||
local repo_name="$REPO_NAME"
|
local repo_name="$REPO_NAME"
|
||||||
[ -z "$repo_name" ] && repo_name=$(basename "$repo_path")
|
[ -z "$repo_name" ] && repo_name=$(basename "$repo_path")
|
||||||
|
|
||||||
print_step "Analyzing repository..."
|
print_step "Analyzing repository..."
|
||||||
|
|
||||||
# Run the analyzer (it outputs to stderr/stdout, results go to /tmp/repo_analysis/)
|
# Run the analyzer (it outputs to stderr/stdout, results go to /tmp/repo_analysis/)
|
||||||
"$ANALYZE_SCRIPT" "$repo_path" >&2 || true
|
"$ANALYZE_SCRIPT" "$repo_path" >&2 || true
|
||||||
|
|
||||||
# Find the results directory
|
# Find the results directory
|
||||||
local results_dir="/tmp/repo_analysis/results_${repo_name}"
|
local results_dir="/tmp/repo_analysis/results_${repo_name}"
|
||||||
if [ ! -d "$results_dir" ]; then
|
if [ ! -d "$results_dir" ]; then
|
||||||
# Try without prefix
|
# Try without prefix
|
||||||
results_dir="/tmp/repo_analysis/results"
|
results_dir="/tmp/repo_analysis/results"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "$results_dir" ] || [ ! -d "$results_dir/per_language" ]; then
|
if [ ! -d "$results_dir" ] || [ ! -d "$results_dir/per_language" ]; then
|
||||||
print_error "Could not find analysis results at $results_dir"
|
print_error "Could not find analysis results at $results_dir"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_success "Analysis complete: $results_dir"
|
print_success "Analysis complete: $results_dir"
|
||||||
echo "$results_dir"
|
echo "$results_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Generate Study Materials
|
# Generate Study Materials
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
generate_materials() {
|
generate_materials() {
|
||||||
local analysis_dir="$1"
|
local analysis_dir="$1"
|
||||||
local output_dir="$2"
|
local output_dir="$2"
|
||||||
|
|
||||||
print_step "Generating study materials with offline documentation..."
|
print_step "Generating study materials with offline documentation..."
|
||||||
|
|
||||||
# Run study materials generator
|
# Run study materials generator
|
||||||
cd "$analysis_dir"
|
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"
|
print_success "Study materials generated"
|
||||||
else
|
else
|
||||||
# Try anyway, might have succeeded
|
# Try anyway, might have succeeded
|
||||||
true
|
true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create output directory and copy results
|
# Create output directory and copy results
|
||||||
mkdir -p "$output_dir"
|
mkdir -p "$output_dir"
|
||||||
|
|
||||||
# Copy generated files
|
# Copy generated files
|
||||||
[ -f "documentation_links.md" ] && cp "documentation_links.md" "$output_dir/"
|
[ -f "documentation_links.md" ] && cp "documentation_links.md" "$output_dir/"
|
||||||
[ -f "anki_cards.txt" ] && cp "anki_cards.txt" "$output_dir/"
|
[ -f "anki_cards.txt" ] && cp "anki_cards.txt" "$output_dir/"
|
||||||
[ -f "llm_anki_prompt.md" ] && cp "llm_anki_prompt.md" "$output_dir/"
|
[ -f "llm_anki_prompt.md" ] && cp "llm_anki_prompt.md" "$output_dir/"
|
||||||
|
|
||||||
# Copy analysis data
|
# Copy analysis data
|
||||||
mkdir -p "$output_dir/analysis"
|
mkdir -p "$output_dir/analysis"
|
||||||
[ -d "per_language" ] && cp -r "per_language" "$output_dir/analysis/"
|
[ -d "per_language" ] && cp -r "per_language" "$output_dir/analysis/"
|
||||||
[ -f "grep_imports.txt" ] && cp "grep_imports.txt" "$output_dir/analysis/"
|
[ -f "grep_imports.txt" ] && cp "grep_imports.txt" "$output_dir/analysis/"
|
||||||
[ -f "grep_keywords.txt" ] && cp "grep_keywords.txt" "$output_dir/analysis/"
|
[ -f "grep_keywords.txt" ] && cp "grep_keywords.txt" "$output_dir/analysis/"
|
||||||
[ -f "grep_function_calls.txt" ] && cp "grep_function_calls.txt" "$output_dir/analysis/"
|
[ -f "grep_function_calls.txt" ] && cp "grep_function_calls.txt" "$output_dir/analysis/"
|
||||||
|
|
||||||
print_success "Files saved to: $output_dir"
|
print_success "Files saved to: $output_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Show Summary
|
# Show Summary
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
show_summary() {
|
show_summary() {
|
||||||
local output_dir="$1"
|
local output_dir="$1"
|
||||||
|
|
||||||
print_header "Study Materials Ready!"
|
print_header "Study Materials Ready!"
|
||||||
|
|
||||||
echo -e "${BOLD}Output directory:${NC} $output_dir"
|
echo -e "${BOLD}Output directory:${NC} $output_dir"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Generated files:${NC}"
|
echo -e "${BOLD}Generated files:${NC}"
|
||||||
|
|
||||||
if [ -f "$output_dir/documentation_links.md" ]; then
|
if [ -f "$output_dir/documentation_links.md" ]; then
|
||||||
local doc_lines
|
local doc_lines
|
||||||
doc_lines=$(wc -l < "$output_dir/documentation_links.md")
|
doc_lines=$(wc -l < "$output_dir/documentation_links.md")
|
||||||
echo -e " 📚 ${GREEN}documentation_links.md${NC} ($doc_lines lines)"
|
echo -e " 📚 ${GREEN}documentation_links.md${NC} ($doc_lines lines)"
|
||||||
echo " Contains links to OFFLINE documentation"
|
echo " Contains links to OFFLINE documentation"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$output_dir/anki_cards.txt" ]; then
|
if [ -f "$output_dir/anki_cards.txt" ]; then
|
||||||
local card_count
|
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 -e " 🎴 ${GREEN}anki_cards.txt${NC} (~$card_count cards)"
|
||||||
echo " Import to Anki: File → Import → Tab separated"
|
echo " Import to Anki: File → Import → Tab separated"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "$output_dir/llm_anki_prompt.md" ]; then
|
if [ -f "$output_dir/llm_anki_prompt.md" ]; then
|
||||||
echo -e " 🤖 ${GREEN}llm_anki_prompt.md${NC}"
|
echo -e " 🤖 ${GREEN}llm_anki_prompt.md${NC}"
|
||||||
echo " Use with ChatGPT/Claude to generate more cards"
|
echo " Use with ChatGPT/Claude to generate more cards"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d "$output_dir/analysis" ]; then
|
if [ -d "$output_dir/analysis" ]; then
|
||||||
echo -e " 📊 ${GREEN}analysis/${NC}"
|
echo -e " 📊 ${GREEN}analysis/${NC}"
|
||||||
echo " Raw analysis data (imports, keywords, functions per language)"
|
echo " Raw analysis data (imports, keywords, functions per language)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Quick preview of imports with offline docs:${NC}"
|
echo -e "${BOLD}Quick preview of imports with offline docs:${NC}"
|
||||||
if [ -f "$output_dir/documentation_links.md" ]; then
|
if [ -f "$output_dir/documentation_links.md" ]; then
|
||||||
grep -A20 "import/from" "$output_dir/documentation_links.md" 2>/dev/null | \
|
grep -A20 "import/from" "$output_dir/documentation_links.md" 2> /dev/null |
|
||||||
grep "^\| \`" | head -5 | \
|
grep "^\| \`" | head -5 |
|
||||||
sed 's/|/│/g'
|
sed 's/|/│/g'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BOLD}Next steps:${NC}"
|
echo -e "${BOLD}Next steps:${NC}"
|
||||||
echo " 1. Open documentation_links.md to browse offline docs"
|
echo " 1. Open documentation_links.md to browse offline docs"
|
||||||
echo " 2. Import anki_cards.txt into Anki for spaced repetition"
|
echo " 2. Import anki_cards.txt into Anki for spaced repetition"
|
||||||
echo " 3. Use llm_anki_prompt.md to generate more targeted cards"
|
echo " 3. Use llm_anki_prompt.md to generate more targeted cards"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}To view a doc:${NC} xdg-open 'file:///path/from/documentation_links.md'"
|
echo -e "${CYAN}To view a doc:${NC} xdg-open 'file:///path/from/documentation_links.md'"
|
||||||
}
|
}
|
||||||
|
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
# Main
|
# Main
|
||||||
#==============================================================================
|
#==============================================================================
|
||||||
main() {
|
main() {
|
||||||
# Handle help
|
# Handle help
|
||||||
if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
if [ $# -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local input="$1"
|
local input="$1"
|
||||||
local output_dir="${2:-}" # Will be set after we know repo name
|
local output_dir="${2:-}" # Will be set after we know repo name
|
||||||
|
|
||||||
print_header "Repo → Study Materials Pipeline"
|
print_header "Repo → Study Materials Pipeline"
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$WORK_DIR"
|
||||||
check_dependencies
|
check_dependencies
|
||||||
ensure_offline_docs
|
ensure_offline_docs
|
||||||
|
|
||||||
# Step 1: Get repository
|
# Step 1: Get repository
|
||||||
print_header "Step 1/3: Getting Repository"
|
print_header "Step 1/3: Getting Repository"
|
||||||
local repo_path
|
local repo_path
|
||||||
repo_path=$(get_repo "$input")
|
repo_path=$(get_repo "$input")
|
||||||
|
|
||||||
# Extract repo name from path (since get_repo runs in subshell, REPO_NAME is lost)
|
# Extract repo name from path (since get_repo runs in subshell, REPO_NAME is lost)
|
||||||
if [ -z "$REPO_NAME" ]; then
|
if [ -z "$REPO_NAME" ]; then
|
||||||
REPO_NAME=$(basename "$repo_path")
|
REPO_NAME=$(basename "$repo_path")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set default output dir based on repo name
|
# Set default output dir based on repo name
|
||||||
if [ -z "$output_dir" ]; then
|
if [ -z "$output_dir" ]; then
|
||||||
output_dir="$STUDY_MATERIALS_BASE/$REPO_NAME"
|
output_dir="$STUDY_MATERIALS_BASE/$REPO_NAME"
|
||||||
elif [[ "$output_dir" != /* ]]; then
|
elif [[ $output_dir != /* ]]; then
|
||||||
# Convert relative to absolute
|
# Convert relative to absolute
|
||||||
output_dir="$(pwd)/$output_dir"
|
output_dir="$(pwd)/$output_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BOLD}Input:${NC} $input" >&2
|
echo -e "${BOLD}Input:${NC} $input" >&2
|
||||||
echo -e "${BOLD}Output:${NC} $output_dir" >&2
|
echo -e "${BOLD}Output:${NC} $output_dir" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
|
|
||||||
# Step 2: Analyze
|
# Step 2: Analyze
|
||||||
print_header "Step 2/3: Analyzing Code"
|
print_header "Step 2/3: Analyzing Code"
|
||||||
local analysis_dir
|
local analysis_dir
|
||||||
analysis_dir=$(analyze_repo "$repo_path")
|
analysis_dir=$(analyze_repo "$repo_path")
|
||||||
|
|
||||||
# Step 3: Generate materials
|
# Step 3: Generate materials
|
||||||
print_header "Step 3/3: Generating Study Materials"
|
print_header "Step 3/3: Generating Study Materials"
|
||||||
generate_materials "$analysis_dir" "$output_dir"
|
generate_materials "$analysis_dir" "$output_dir"
|
||||||
|
|
||||||
# Show results
|
# Show results
|
||||||
show_summary "$output_dir"
|
show_summary "$output_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -13,36 +13,36 @@ source "$SCRIPT_DIR/../lib/android.sh"
|
|||||||
init_android_script "$@"
|
init_android_script "$@"
|
||||||
|
|
||||||
install_adaway() {
|
install_adaway() {
|
||||||
print_header "Installing AdAway"
|
print_header "Installing AdAway"
|
||||||
|
|
||||||
local adaway_apk="$WORK_DIR/adaway.apk"
|
local adaway_apk="$WORK_DIR/adaway.apk"
|
||||||
local adaway_url="https://github.com/AdAway/AdAway/releases/latest/download/AdAway.apk"
|
local adaway_url="https://github.com/AdAway/AdAway/releases/latest/download/AdAway.apk"
|
||||||
|
|
||||||
if [[ ! -f $adaway_apk ]]; then
|
if [[ ! -f $adaway_apk ]]; then
|
||||||
log "Downloading AdAway APK..."
|
log "Downloading AdAway APK..."
|
||||||
curl -L -o "$adaway_apk" "$adaway_url" || die "Failed to download AdAway"
|
curl -L -o "$adaway_apk" "$adaway_url" || die "Failed to download AdAway"
|
||||||
else
|
else
|
||||||
log "AdAway APK already downloaded"
|
log "AdAway APK already downloaded"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Installing AdAway..."
|
log "Installing AdAway..."
|
||||||
if adb install -r "$adaway_apk" 2>&1 | grep -q "Success"; then
|
if adb install -r "$adaway_apk" 2>&1 | grep -q "Success"; then
|
||||||
log "AdAway installed successfully"
|
log "AdAway installed successfully"
|
||||||
else
|
else
|
||||||
warn "AdAway installation may have failed or already installed"
|
warn "AdAway installation may have failed or already installed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_systemless_hosts() {
|
setup_systemless_hosts() {
|
||||||
print_header "Setting up Systemless Hosts"
|
print_header "Setting up Systemless Hosts"
|
||||||
|
|
||||||
log "Installing Systemless Hosts module..."
|
log "Installing Systemless Hosts module..."
|
||||||
|
|
||||||
# Create systemless hosts module directory
|
# Create systemless hosts module directory
|
||||||
adb shell "su -c 'mkdir -p /data/adb/modules/systemless_hosts/system/etc'" || die "Failed to create module directory"
|
adb shell "su -c 'mkdir -p /data/adb/modules/systemless_hosts/system/etc'" || die "Failed to create module directory"
|
||||||
|
|
||||||
# Create module.prop
|
# Create module.prop
|
||||||
cat >"$WORK_DIR/module.prop" <<'EOF'
|
cat > "$WORK_DIR/module.prop" << 'EOF'
|
||||||
id=systemless_hosts
|
id=systemless_hosts
|
||||||
name=Systemless Hosts
|
name=Systemless Hosts
|
||||||
version=1.0
|
version=1.0
|
||||||
@ -51,122 +51,122 @@ author=Custom
|
|||||||
description=Custom hosts file from StevenBlack with extensions
|
description=Custom hosts file from StevenBlack with extensions
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
adb push "$WORK_DIR/module.prop" /sdcard/module.prop
|
adb push "$WORK_DIR/module.prop" /sdcard/module.prop
|
||||||
adb shell "su -c 'cp /sdcard/module.prop /data/adb/modules/systemless_hosts/'" || die "Failed to create module.prop"
|
adb shell "su -c 'cp /sdcard/module.prop /data/adb/modules/systemless_hosts/'" || die "Failed to create module.prop"
|
||||||
adb shell "su -c 'rm /sdcard/module.prop'"
|
adb shell "su -c 'rm /sdcard/module.prop'"
|
||||||
|
|
||||||
log "Module structure created"
|
log "Module structure created"
|
||||||
}
|
}
|
||||||
|
|
||||||
push_hosts_file() {
|
push_hosts_file() {
|
||||||
print_header "Pushing Custom Hosts File"
|
print_header "Pushing Custom Hosts File"
|
||||||
|
|
||||||
local hosts_file="$WORK_DIR/hosts"
|
local hosts_file="$WORK_DIR/hosts"
|
||||||
|
|
||||||
# Use the StevenBlack cache or generate from /etc/hosts
|
# Use the StevenBlack cache or generate from /etc/hosts
|
||||||
if [[ -f /etc/hosts.stevenblack ]]; then
|
if [[ -f /etc/hosts.stevenblack ]]; then
|
||||||
log "Using StevenBlack hosts cache..."
|
log "Using StevenBlack hosts cache..."
|
||||||
cp /etc/hosts.stevenblack "$hosts_file"
|
cp /etc/hosts.stevenblack "$hosts_file"
|
||||||
elif [[ -f /etc/hosts ]]; then
|
elif [[ -f /etc/hosts ]]; then
|
||||||
log "Using current /etc/hosts..."
|
log "Using current /etc/hosts..."
|
||||||
cp /etc/hosts "$hosts_file"
|
cp /etc/hosts "$hosts_file"
|
||||||
else
|
else
|
||||||
die "No hosts file found"
|
die "No hosts file found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Show stats
|
# Show stats
|
||||||
local total_entries
|
local total_entries
|
||||||
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
|
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
|
||||||
log "Hosts file contains $total_entries blocked domains"
|
log "Hosts file contains $total_entries blocked domains"
|
||||||
|
|
||||||
log "Pushing hosts file to device..."
|
log "Pushing hosts file to device..."
|
||||||
adb push "$hosts_file" /sdcard/hosts || die "Failed to push hosts file"
|
adb push "$hosts_file" /sdcard/hosts || die "Failed to push hosts file"
|
||||||
|
|
||||||
log "Installing hosts file systemlessly..."
|
log "Installing hosts file systemlessly..."
|
||||||
adb shell "su -c 'cp /sdcard/hosts /data/adb/modules/systemless_hosts/system/etc/hosts'" || die "Failed to install hosts file"
|
adb shell "su -c 'cp /sdcard/hosts /data/adb/modules/systemless_hosts/system/etc/hosts'" || die "Failed to install hosts file"
|
||||||
adb shell "su -c 'chmod 644 /data/adb/modules/systemless_hosts/system/etc/hosts'" || die "Failed to set permissions"
|
adb shell "su -c 'chmod 644 /data/adb/modules/systemless_hosts/system/etc/hosts'" || die "Failed to set permissions"
|
||||||
adb shell "su -c 'rm /sdcard/hosts'"
|
adb shell "su -c 'rm /sdcard/hosts'"
|
||||||
|
|
||||||
log "Hosts file installed successfully"
|
log "Hosts file installed successfully"
|
||||||
log "Total blocked domains: $total_entries"
|
log "Total blocked domains: $total_entries"
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_module() {
|
enable_module() {
|
||||||
print_header "Enabling Systemless Hosts Module"
|
print_header "Enabling Systemless Hosts Module"
|
||||||
|
|
||||||
log "Removing disable flag if present..."
|
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/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/remove'" 2> /dev/null || true
|
||||||
|
|
||||||
log "Module enabled"
|
log "Module enabled"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo " REBOOT REQUIRED"
|
echo " REBOOT REQUIRED"
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo
|
echo
|
||||||
echo "The systemless hosts module requires a reboot to take effect."
|
echo "The systemless hosts module requires a reboot to take effect."
|
||||||
echo
|
echo
|
||||||
read -p "Reboot device now? [y/N]: " -n 1 -r
|
read -p "Reboot device now? [y/N]: " -n 1 -r
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
log "Rebooting device..."
|
log "Rebooting device..."
|
||||||
adb reboot
|
adb reboot
|
||||||
log "Device rebooting. Wait for boot to complete."
|
log "Device rebooting. Wait for boot to complete."
|
||||||
else
|
else
|
||||||
warn "Remember to reboot manually for changes to take effect!"
|
warn "Remember to reboot manually for changes to take effect!"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_hosts() {
|
verify_hosts() {
|
||||||
print_header "Verifying Hosts Installation"
|
print_header "Verifying Hosts Installation"
|
||||||
|
|
||||||
log "Waiting for device to boot..."
|
log "Waiting for device to boot..."
|
||||||
sleep 5
|
sleep 5
|
||||||
adb wait-for-device
|
adb wait-for-device
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
log "Checking if hosts file is active..."
|
log "Checking if hosts file is active..."
|
||||||
local test_domain="doubleclick.net"
|
local test_domain="doubleclick.net"
|
||||||
local result
|
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
|
if [[ $result -gt 0 ]]; then
|
||||||
log "✓ Hosts file is active and blocking domains"
|
log "✓ Hosts file is active and blocking domains"
|
||||||
else
|
else
|
||||||
warn "Could not verify hosts file, but module should be installed"
|
warn "Could not verify hosts file, but module should be installed"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
print_header "Android Ad Blocking Setup"
|
print_header "Android Ad Blocking Setup"
|
||||||
|
|
||||||
check_device
|
check_device
|
||||||
check_root
|
check_root
|
||||||
|
|
||||||
echo "This will:"
|
echo "This will:"
|
||||||
echo " 1. Install AdAway app (optional GUI management)"
|
echo " 1. Install AdAway app (optional GUI management)"
|
||||||
echo " 2. Create systemless hosts module"
|
echo " 2. Create systemless hosts module"
|
||||||
echo " 3. Push your custom hosts file (StevenBlack with extensions)"
|
echo " 3. Push your custom hosts file (StevenBlack with extensions)"
|
||||||
echo " 4. Enable the module and reboot"
|
echo " 4. Enable the module and reboot"
|
||||||
echo
|
echo
|
||||||
read -p "Continue? [y/N]: " -n 1 -r
|
read -p "Continue? [y/N]: " -n 1 -r
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
log "Cancelled by user"
|
log "Cancelled by user"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_adaway
|
install_adaway
|
||||||
setup_systemless_hosts
|
setup_systemless_hosts
|
||||||
push_hosts_file
|
push_hosts_file
|
||||||
enable_module
|
enable_module
|
||||||
|
|
||||||
log "Setup complete!"
|
log "Setup complete!"
|
||||||
log "After reboot, ads should be blocked system-wide"
|
log "After reboot, ads should be blocked system-wide"
|
||||||
log "You can manage hosts in the AdAway app or by updating the module"
|
log "You can manage hosts in the AdAway app or by updating the module"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -12,33 +12,33 @@ SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
|
|||||||
|
|
||||||
# Get the actual user (not root when running with sudo)
|
# Get the actual user (not root when running with sudo)
|
||||||
if [[ -n ${SUDO_USER:-} ]]; then
|
if [[ -n ${SUDO_USER:-} ]]; then
|
||||||
USER_NAME="$SUDO_USER"
|
USER_NAME="$SUDO_USER"
|
||||||
else
|
else
|
||||||
USER_NAME="$(whoami)"
|
USER_NAME="$(whoami)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Function to log messages
|
# Function to log messages
|
||||||
log() {
|
log() {
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if organize script exists
|
# Check if organize script exists
|
||||||
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
|
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
|
||||||
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
|
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if running as root for systemd service creation
|
# Check if running as root for systemd service creation
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
log "This script needs to be run as root to create systemd service."
|
log "This script needs to be run as root to create systemd service."
|
||||||
log "Re-executing with sudo..."
|
log "Re-executing with sudo..."
|
||||||
exec sudo "$0" "$@"
|
exec sudo "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Setting up media organizer startup service..."
|
log "Setting up media organizer startup service..."
|
||||||
|
|
||||||
# Create systemd service file
|
# Create systemd service file
|
||||||
cat >"$SERVICE_FILE" <<EOF
|
cat > "$SERVICE_FILE" << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Media File Organizer
|
Description=Media File Organizer
|
||||||
After=graphical-session.target
|
After=graphical-session.target
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -14,10 +14,10 @@ set -euo pipefail
|
|||||||
# Source common library if available
|
# Source common library if available
|
||||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
|
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
|
||||||
# shellcheck source=../lib/common.sh
|
# shellcheck source=../lib/common.sh
|
||||||
source "$SCRIPT_DIR/../lib/common.sh"
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
else
|
else
|
||||||
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@ -25,34 +25,34 @@ KEEPASS_DIR="${1:-$HOME/Keepass}"
|
|||||||
BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)"
|
BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)"
|
||||||
|
|
||||||
# Ensure keepassxc-cli is installed
|
# 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"
|
log "ERROR: 'keepassxc-cli' is not installed. Install with: sudo pacman -S keepassxc"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if directory exists
|
# Check if directory exists
|
||||||
if [[ ! -d "$KEEPASS_DIR" ]]; then
|
if [[ ! -d $KEEPASS_DIR ]]; then
|
||||||
log "ERROR: Directory does not exist: $KEEPASS_DIR"
|
log "ERROR: Directory does not exist: $KEEPASS_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find all .kdbx files
|
# Find all .kdbx files
|
||||||
mapfile -t KDBX_FILES < <(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | sort)
|
mapfile -t KDBX_FILES < <(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | sort)
|
||||||
|
|
||||||
if [[ ${#KDBX_FILES[@]} -eq 0 ]]; then
|
if [[ ${#KDBX_FILES[@]} -eq 0 ]]; then
|
||||||
log "No .kdbx files found in $KEEPASS_DIR"
|
log "No .kdbx files found in $KEEPASS_DIR"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${#KDBX_FILES[@]} -eq 1 ]]; then
|
if [[ ${#KDBX_FILES[@]} -eq 1 ]]; then
|
||||||
log "Only one .kdbx file found. Nothing to merge."
|
log "Only one .kdbx file found. Nothing to merge."
|
||||||
log "File: ${KDBX_FILES[0]}"
|
log "File: ${KDBX_FILES[0]}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Found ${#KDBX_FILES[@]} .kdbx files in $KEEPASS_DIR:"
|
log "Found ${#KDBX_FILES[@]} .kdbx files in $KEEPASS_DIR:"
|
||||||
for f in "${KDBX_FILES[@]}"; do
|
for f in "${KDBX_FILES[@]}"; do
|
||||||
echo " - $(basename "$f")"
|
echo " - $(basename "$f")"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Create backup directory
|
# Create backup directory
|
||||||
@ -61,7 +61,7 @@ log "Creating backups in: $BACKUP_DIR"
|
|||||||
|
|
||||||
# Backup all files before any operation
|
# Backup all files before any operation
|
||||||
for f in "${KDBX_FILES[@]}"; do
|
for f in "${KDBX_FILES[@]}"; do
|
||||||
cp -v "$f" "$BACKUP_DIR/"
|
cp -v "$f" "$BACKUP_DIR/"
|
||||||
done
|
done
|
||||||
log "All files backed up successfully."
|
log "All files backed up successfully."
|
||||||
|
|
||||||
@ -74,9 +74,9 @@ echo "Backups are stored in: $BACKUP_DIR"
|
|||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Do you want to continue? (yes/no): " CONFIRM
|
read -rp "Do you want to continue? (yes/no): " CONFIRM
|
||||||
if [[ "$CONFIRM" != "yes" ]]; then
|
if [[ $CONFIRM != "yes" ]]; then
|
||||||
log "Aborted by user."
|
log "Aborted by user."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Select the target database (the one to merge INTO)
|
# Select the target database (the one to merge INTO)
|
||||||
@ -93,9 +93,9 @@ read -rsp "Enter master password for TARGET database ($(basename "$TARGET_DB")):
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Verify target password works
|
# 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?"
|
log "ERROR: Failed to open target database. Wrong password?"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
log "Target database password verified."
|
log "Target database password verified."
|
||||||
|
|
||||||
@ -107,41 +107,41 @@ SAME_PASSWORD="${SAME_PASSWORD,,}" # lowercase
|
|||||||
# Merge each source database into the target
|
# Merge each source database into the target
|
||||||
MERGE_COUNT=0
|
MERGE_COUNT=0
|
||||||
for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do
|
for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do
|
||||||
SOURCE_DB="${KDBX_FILES[$i]}"
|
SOURCE_DB="${KDBX_FILES[$i]}"
|
||||||
log ""
|
log ""
|
||||||
log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..."
|
log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..."
|
||||||
|
|
||||||
# Reuse target password if user confirmed all are the same
|
# 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"
|
SOURCE_PASSWORD="$TARGET_PASSWORD"
|
||||||
else
|
else
|
||||||
# Ask for source password (might be different)
|
# Ask for source password (might be different)
|
||||||
echo ""
|
echo ""
|
||||||
read -rsp "Enter master password for SOURCE database ($(basename "$SOURCE_DB")): " SOURCE_PASSWORD
|
read -rsp "Enter master password for SOURCE database ($(basename "$SOURCE_DB")): " SOURCE_PASSWORD
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify source password
|
# 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 "ERROR: Failed to open source database $(basename "$SOURCE_DB"). Wrong password?"
|
||||||
log "Skipping this database. You can try again later."
|
log "Skipping this database. You can try again later."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Perform the merge
|
# Perform the merge
|
||||||
# keepassxc-cli merge requires: target_db source_db
|
# keepassxc-cli merge requires: target_db source_db
|
||||||
# It will prompt for passwords
|
# It will prompt for passwords
|
||||||
if echo -e "${TARGET_PASSWORD}\n${SOURCE_PASSWORD}" | keepassxc-cli merge "$TARGET_DB" "$SOURCE_DB"; then
|
if echo -e "${TARGET_PASSWORD}\n${SOURCE_PASSWORD}" | keepassxc-cli merge "$TARGET_DB" "$SOURCE_DB"; then
|
||||||
log "Successfully merged $(basename "$SOURCE_DB")"
|
log "Successfully merged $(basename "$SOURCE_DB")"
|
||||||
|
|
||||||
# Delete the source database after successful merge
|
# Delete the source database after successful merge
|
||||||
log "Deleting source database: $(basename "$SOURCE_DB")"
|
log "Deleting source database: $(basename "$SOURCE_DB")"
|
||||||
rm -v "$SOURCE_DB"
|
rm -v "$SOURCE_DB"
|
||||||
((MERGE_COUNT++)) || true
|
((MERGE_COUNT++)) || true
|
||||||
else
|
else
|
||||||
log "ERROR: Failed to merge $(basename "$SOURCE_DB")"
|
log "ERROR: Failed to merge $(basename "$SOURCE_DB")"
|
||||||
log "Source database NOT deleted. Check the backup and try manually."
|
log "Source database NOT deleted. Check the backup and try manually."
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@ -159,15 +159,15 @@ find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f -exec basename {} \;
|
|||||||
# Rename to clean name if desired
|
# Rename to clean name if desired
|
||||||
FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l)
|
FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l)
|
||||||
if [[ $FINAL_COUNT -eq 1 ]]; then
|
if [[ $FINAL_COUNT -eq 1 ]]; then
|
||||||
log ""
|
log ""
|
||||||
FINAL_NAME="$KEEPASS_DIR/Passwords.kdbx"
|
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
|
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"
|
mv -v "$TARGET_DB" "$FINAL_NAME"
|
||||||
log "Final database: $FINAL_NAME"
|
log "Final database: $FINAL_NAME"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
log ""
|
log ""
|
||||||
log "SUCCESS: You now have exactly ONE KeePassXC database!"
|
log "SUCCESS: You now have exactly ONE KeePassXC database!"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -10,34 +10,34 @@ source "$SCRIPT_DIR/../lib/common.sh"
|
|||||||
# Configuration -----------------------------------------------------------------
|
# Configuration -----------------------------------------------------------------
|
||||||
TARGET_SESSION_NAME="Xfce Session"
|
TARGET_SESSION_NAME="Xfce Session"
|
||||||
TARGET_PACKAGES=(
|
TARGET_PACKAGES=(
|
||||||
xfwm4 # Compositing window manager with XFCE integration
|
xfwm4 # Compositing window manager with XFCE integration
|
||||||
xfce4-session # Provides the Xfce session entry for display managers
|
xfce4-session # Provides the Xfce session entry for display managers
|
||||||
xfce4-panel # Panel with system tray support
|
xfce4-panel # Panel with system tray support
|
||||||
xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.)
|
xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.)
|
||||||
xfce4-terminal # Handy default terminal for the new environment
|
xfce4-terminal # Handy default terminal for the new environment
|
||||||
)
|
)
|
||||||
|
|
||||||
# Utility functions --------------------------------------------------------------
|
# Utility functions --------------------------------------------------------------
|
||||||
info() { echo "[INFO] $*"; }
|
info() { echo "[INFO] $*"; }
|
||||||
warn() { echo "[WARN] $*" >&2; }
|
warn() { echo "[WARN] $*" >&2; }
|
||||||
error() {
|
error() {
|
||||||
echo "[ERROR] $*" >&2
|
echo "[ERROR] $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_pacman() {
|
ensure_pacman() {
|
||||||
require_command pacman "pacman" || error "Required command 'pacman' not found."
|
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."
|
warn "This script was designed for Arch Linux; continuing anyway."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_packages() {
|
install_packages() {
|
||||||
install_missing_pacman_packages "${TARGET_PACKAGES[@]}"
|
install_missing_pacman_packages "${TARGET_PACKAGES[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
print_post_install_tips() {
|
print_post_install_tips() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
XFCE session installed.
|
XFCE session installed.
|
||||||
@ -54,31 +54,31 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout_user() {
|
logout_user() {
|
||||||
local session_id="${XDG_SESSION_ID:-}"
|
local session_id="${XDG_SESSION_ID:-}"
|
||||||
|
|
||||||
if [[ -n $session_id ]] && loginctl show-session "$session_id" >/dev/null 2>&1; then
|
if [[ -n $session_id ]] && loginctl show-session "$session_id" > /dev/null 2>&1; then
|
||||||
info "Terminating current session (ID: $session_id) via loginctl."
|
info "Terminating current session (ID: $session_id) via loginctl."
|
||||||
loginctl terminate-session "$session_id"
|
loginctl terminate-session "$session_id"
|
||||||
return
|
return
|
||||||
fi
|
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."
|
info "Terminating all sessions for user '$USER' via loginctl."
|
||||||
loginctl terminate-user "$USER"
|
loginctl terminate-user "$USER"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
warn "loginctl could not terminate the session; attempting fallback logout."
|
warn "loginctl could not terminate the session; attempting fallback logout."
|
||||||
pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually."
|
pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually."
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
ensure_pacman
|
ensure_pacman
|
||||||
install_packages
|
install_packages
|
||||||
print_post_install_tips
|
print_post_install_tips
|
||||||
|
|
||||||
# Give the user a moment to read the instructions before logging out.
|
# Give the user a moment to read the instructions before logging out.
|
||||||
logout_user
|
logout_user
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@ -16,7 +16,7 @@ DEFAULT_RESOLUTION="320x240"
|
|||||||
|
|
||||||
# Function to display usage
|
# Function to display usage
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage: $0 <input_text_file> [resolution] [output_prefix]
|
Usage: $0 <input_text_file> [resolution] [output_prefix]
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@ -31,7 +31,7 @@ Examples:
|
|||||||
|
|
||||||
Note: Requires ImageMagick (magick or convert command)
|
Note: Requires ImageMagick (magick or convert command)
|
||||||
EOF
|
EOF
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if ImageMagick is installed and determine which command to use
|
# Check if ImageMagick is installed and determine which command to use
|
||||||
@ -39,8 +39,8 @@ require_imagemagick || exit 1
|
|||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
echo "Error: Missing required argument <input_text_file>"
|
echo "Error: Missing required argument <input_text_file>"
|
||||||
usage
|
usage
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INPUT_FILE="$1"
|
INPUT_FILE="$1"
|
||||||
@ -49,15 +49,15 @@ OUTPUT_PREFIX="${3:-}"
|
|||||||
|
|
||||||
# Validate input file exists
|
# Validate input file exists
|
||||||
if [[ ! -f ${INPUT_FILE} ]]; then
|
if [[ ! -f ${INPUT_FILE} ]]; then
|
||||||
echo "Error: Input file '${INPUT_FILE}' does not exist."
|
echo "Error: Input file '${INPUT_FILE}' does not exist."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate resolution format (WIDTHxHEIGHT)
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
if ! validate_resolution "$RESOLUTION"; then
|
if ! validate_resolution "$RESOLUTION"; then
|
||||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract width and height
|
# Extract width and height
|
||||||
@ -67,22 +67,22 @@ HEIGHT=$(echo "${RESOLUTION}" | cut -d'x' -f2)
|
|||||||
# Calculate font size based on resolution
|
# Calculate font size based on resolution
|
||||||
FONT_SIZE=$((WIDTH / 30))
|
FONT_SIZE=$((WIDTH / 30))
|
||||||
if [[ ${FONT_SIZE} -lt 8 ]]; then
|
if [[ ${FONT_SIZE} -lt 8 ]]; then
|
||||||
FONT_SIZE=8
|
FONT_SIZE=8
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate output prefix if not provided
|
# Generate output prefix if not provided
|
||||||
if [[ -z ${OUTPUT_PREFIX} ]]; then
|
if [[ -z ${OUTPUT_PREFIX} ]]; then
|
||||||
BASENAME=$(basename "${INPUT_FILE}")
|
BASENAME=$(basename "${INPUT_FILE}")
|
||||||
FILENAME="${BASENAME%.*}"
|
FILENAME="${BASENAME%.*}"
|
||||||
DIRNAME=$(dirname "${INPUT_FILE}")
|
DIRNAME=$(dirname "${INPUT_FILE}")
|
||||||
OUTPUT_PREFIX="${DIRNAME}/${FILENAME}"
|
OUTPUT_PREFIX="${DIRNAME}/${FILENAME}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Calculate lines per image based on resolution and font size
|
# Calculate lines per image based on resolution and font size
|
||||||
# Rough estimate: height / (font_size * 1.5) for line spacing
|
# Rough estimate: height / (font_size * 1.5) for line spacing
|
||||||
LINES_PER_IMAGE=$((HEIGHT / (FONT_SIZE * 3 / 2)))
|
LINES_PER_IMAGE=$((HEIGHT / (FONT_SIZE * 3 / 2)))
|
||||||
if [[ ${LINES_PER_IMAGE} -lt 5 ]]; then
|
if [[ ${LINES_PER_IMAGE} -lt 5 ]]; then
|
||||||
LINES_PER_IMAGE=5
|
LINES_PER_IMAGE=5
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Converting text file to image(s)..."
|
echo "Converting text file to image(s)..."
|
||||||
@ -91,7 +91,7 @@ echo "Font size: ${FONT_SIZE}"
|
|||||||
echo "Estimated lines per image: ${LINES_PER_IMAGE}"
|
echo "Estimated lines per image: ${LINES_PER_IMAGE}"
|
||||||
|
|
||||||
# Read the file and count total lines
|
# Read the file and count total lines
|
||||||
mapfile -t LINES <"${INPUT_FILE}"
|
mapfile -t LINES < "${INPUT_FILE}"
|
||||||
TOTAL_LINES=${#LINES[@]}
|
TOTAL_LINES=${#LINES[@]}
|
||||||
|
|
||||||
echo "Total lines in file: ${TOTAL_LINES}"
|
echo "Total lines in file: ${TOTAL_LINES}"
|
||||||
@ -108,53 +108,53 @@ trap 'rm -rf ${TEMP_DIR}' EXIT
|
|||||||
# Split text into chunks and create images
|
# Split text into chunks and create images
|
||||||
IMAGE_COUNT=0
|
IMAGE_COUNT=0
|
||||||
for ((i = 0; i < TOTAL_LINES; i += LINES_PER_IMAGE)); do
|
for ((i = 0; i < TOTAL_LINES; i += LINES_PER_IMAGE)); do
|
||||||
IMAGE_COUNT=$((IMAGE_COUNT + 1))
|
IMAGE_COUNT=$((IMAGE_COUNT + 1))
|
||||||
|
|
||||||
# Calculate end line for this chunk
|
# Calculate end line for this chunk
|
||||||
END_LINE=$((i + LINES_PER_IMAGE))
|
END_LINE=$((i + LINES_PER_IMAGE))
|
||||||
if [[ ${END_LINE} -gt ${TOTAL_LINES} ]]; then
|
if [[ ${END_LINE} -gt ${TOTAL_LINES} ]]; then
|
||||||
END_LINE=${TOTAL_LINES}
|
END_LINE=${TOTAL_LINES}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create chunk file
|
# Create chunk file
|
||||||
CHUNK_FILE="${TEMP_DIR}/chunk_${IMAGE_COUNT}.txt"
|
CHUNK_FILE="${TEMP_DIR}/chunk_${IMAGE_COUNT}.txt"
|
||||||
for ((j = i; j < END_LINE; j++)); do
|
for ((j = i; j < END_LINE; j++)); do
|
||||||
echo "${LINES[$j]}" >>"${CHUNK_FILE}"
|
echo "${LINES[$j]}" >> "${CHUNK_FILE}"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Determine output filename
|
# Determine output filename
|
||||||
if [[ ${NUM_IMAGES} -eq 1 ]]; then
|
if [[ ${NUM_IMAGES} -eq 1 ]]; then
|
||||||
OUTPUT_FILE="${OUTPUT_PREFIX}.png"
|
OUTPUT_FILE="${OUTPUT_PREFIX}.png"
|
||||||
else
|
else
|
||||||
OUTPUT_FILE="${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png"
|
OUTPUT_FILE="${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " Creating image ${IMAGE_COUNT}/${NUM_IMAGES}: ${OUTPUT_FILE}"
|
echo " Creating image ${IMAGE_COUNT}/${NUM_IMAGES}: ${OUTPUT_FILE}"
|
||||||
|
|
||||||
# Create image from text
|
# Create image from text
|
||||||
# Using label: instead of caption: for better control
|
# Using label: instead of caption: for better control
|
||||||
if ${MAGICK_CMD} -size "${WIDTH}x${HEIGHT}" \
|
if ${MAGICK_CMD} -size "${WIDTH}x${HEIGHT}" \
|
||||||
-background white \
|
-background white \
|
||||||
-fill black \
|
-fill black \
|
||||||
-font "DejaVu-Sans-Mono" \
|
-font "DejaVu-Sans-Mono" \
|
||||||
-pointsize "${FONT_SIZE}" \
|
-pointsize "${FONT_SIZE}" \
|
||||||
-gravity northwest \
|
-gravity northwest \
|
||||||
label:@"${CHUNK_FILE}" \
|
label:@"${CHUNK_FILE}" \
|
||||||
-extent "${WIDTH}x${HEIGHT}" \
|
-extent "${WIDTH}x${HEIGHT}" \
|
||||||
"${OUTPUT_FILE}"; then
|
"${OUTPUT_FILE}"; then
|
||||||
OUTPUT_SIZE=$(du -h "${OUTPUT_FILE}" | cut -f1)
|
OUTPUT_SIZE=$(du -h "${OUTPUT_FILE}" | cut -f1)
|
||||||
echo " ✓ Created: ${OUTPUT_FILE} (${OUTPUT_SIZE})"
|
echo " ✓ Created: ${OUTPUT_FILE} (${OUTPUT_SIZE})"
|
||||||
else
|
else
|
||||||
echo " ✗ Failed to create: ${OUTPUT_FILE}"
|
echo " ✗ Failed to create: ${OUTPUT_FILE}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Successfully created ${IMAGE_COUNT} image(s)"
|
echo "✓ Successfully created ${IMAGE_COUNT} image(s)"
|
||||||
echo "Output files:"
|
echo "Output files:"
|
||||||
if [[ ${NUM_IMAGES} -eq 1 ]]; then
|
if [[ ${NUM_IMAGES} -eq 1 ]]; then
|
||||||
echo " ${OUTPUT_PREFIX}.png"
|
echo " ${OUTPUT_PREFIX}.png"
|
||||||
else
|
else
|
||||||
echo " ${OUTPUT_PREFIX}_001.png to ${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png"
|
echo " ${OUTPUT_PREFIX}_001.png to ${OUTPUT_PREFIX}_$(printf "%03d" ${IMAGE_COUNT}).png"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -16,32 +16,32 @@ MODULE_DEST="/data/adb/modules/android_guardian"
|
|||||||
|
|
||||||
# Ensure android-tools (adb) is installed
|
# Ensure android-tools (adb) is installed
|
||||||
ensure_adb_installed() {
|
ensure_adb_installed() {
|
||||||
if command -v adb &>/dev/null; then
|
if command -v adb &> /dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "adb not found, installing android-tools..."
|
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"
|
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"
|
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"
|
sudo dnf install -y android-tools || die "Failed to install android-tools"
|
||||||
else
|
else
|
||||||
die "adb not found and could not determine package manager. Please install android-tools manually."
|
die "adb not found and could not determine package manager. Please install android-tools manually."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Verify installation
|
# Verify installation
|
||||||
if ! command -v adb &>/dev/null; then
|
if ! command -v adb &> /dev/null; then
|
||||||
die "adb installation failed"
|
die "adb installation failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "android-tools installed successfully"
|
log "android-tools installed successfully"
|
||||||
}
|
}
|
||||||
|
|
||||||
show_usage() {
|
show_usage() {
|
||||||
cat <<EOF
|
cat << EOF
|
||||||
Usage: $(basename "$0") [COMMAND]
|
Usage: $(basename "$0") [COMMAND]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
@ -81,239 +81,239 @@ WIRELESS_CONFIG="$HOME/.config/android_guardian_wireless"
|
|||||||
|
|
||||||
# Discover Android devices on the network using mDNS
|
# Discover Android devices on the network using mDNS
|
||||||
discover_android_device() {
|
discover_android_device() {
|
||||||
local found_address=""
|
local found_address=""
|
||||||
|
|
||||||
# Ensure avahi-browse is available
|
# Ensure avahi-browse is available
|
||||||
if ! command -v avahi-browse &>/dev/null; then
|
if ! command -v avahi-browse &> /dev/null; then
|
||||||
if command -v pacman &>/dev/null; then
|
if command -v pacman &> /dev/null; then
|
||||||
echo "Installing avahi for device discovery..." >&2
|
echo "Installing avahi for device discovery..." >&2
|
||||||
sudo pacman -S --noconfirm avahi nss-mdns &>/dev/null || true
|
sudo pacman -S --noconfirm avahi nss-mdns &> /dev/null || true
|
||||||
sudo systemctl enable --now avahi-daemon &>/dev/null || true
|
sudo systemctl enable --now avahi-daemon &> /dev/null || true
|
||||||
elif command -v apt-get &>/dev/null; then
|
elif command -v apt-get &> /dev/null; then
|
||||||
sudo apt-get install -y avahi-utils &>/dev/null || true
|
sudo apt-get install -y avahi-utils &> /dev/null || true
|
||||||
fi
|
fi
|
||||||
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
|
echo "Scanning for Android devices (5 seconds)..." >&2
|
||||||
|
|
||||||
# Android wireless debugging advertises as _adb-tls-connect._tcp
|
# Android wireless debugging advertises as _adb-tls-connect._tcp
|
||||||
local discovery_result
|
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;...
|
# Parse: =;eth0;IPv4;adb-...;_adb-tls-connect._tcp;local;hostname.local;192.168.x.x;port;...
|
||||||
local ip port
|
local ip port
|
||||||
ip=$(echo "$discovery_result" | cut -d';' -f8)
|
ip=$(echo "$discovery_result" | cut -d';' -f8)
|
||||||
port=$(echo "$discovery_result" | cut -d';' -f9)
|
port=$(echo "$discovery_result" | cut -d';' -f9)
|
||||||
|
|
||||||
if [[ -n "$ip" && -n "$port" ]]; then
|
if [[ -n $ip && -n $port ]]; then
|
||||||
found_address="$ip:$port"
|
found_address="$ip:$port"
|
||||||
echo "✓ Found device: $found_address" >&2
|
echo "✓ Found device: $found_address" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fallback: try adb's mdns discovery
|
# Fallback: try adb's mdns discovery
|
||||||
if [[ -z "$found_address" ]]; then
|
if [[ -z $found_address ]]; then
|
||||||
echo "Trying adb mdns discovery..." >&2
|
echo "Trying adb mdns discovery..." >&2
|
||||||
|
|
||||||
# adb can discover devices via mdns
|
# adb can discover devices via mdns
|
||||||
local mdns_result
|
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
|
# Try to extract IP:port from the result
|
||||||
local service_name
|
local service_name
|
||||||
service_name=$(echo "$mdns_result" | awk '{print $1}')
|
service_name=$(echo "$mdns_result" | awk '{print $1}')
|
||||||
if [[ -n "$service_name" ]]; then
|
if [[ -n $service_name ]]; then
|
||||||
# Try connecting via service name
|
# Try connecting via service name
|
||||||
echo "Found service: $service_name" >&2
|
echo "Found service: $service_name" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Return found address (or empty)
|
# Return found address (or empty)
|
||||||
echo "$found_address"
|
echo "$found_address"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Pair with device over WiFi (Android 11+)
|
# Pair with device over WiFi (Android 11+)
|
||||||
cmd_pair() {
|
cmd_pair() {
|
||||||
ensure_adb_installed
|
ensure_adb_installed
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Wireless ADB Pairing (Android 11+) ==="
|
echo "=== Wireless ADB Pairing (Android 11+) ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "On your phone:"
|
echo "On your phone:"
|
||||||
echo " 1. Go to Settings > Developer Options > Wireless debugging"
|
echo " 1. Go to Settings > Developer Options > Wireless debugging"
|
||||||
echo " 2. Enable Wireless debugging"
|
echo " 2. Enable Wireless debugging"
|
||||||
echo " 3. Tap 'Pair device with pairing code'"
|
echo " 3. Tap 'Pair device with pairing code'"
|
||||||
echo " 4. Note the IP:port and pairing code shown"
|
echo " 4. Note the IP:port and pairing code shown"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
read -rp "Enter pairing IP:port (e.g., 192.168.1.100:37123): " pair_address
|
read -rp "Enter pairing IP:port (e.g., 192.168.1.100:37123): " pair_address
|
||||||
read -rp "Enter pairing code: " pair_code
|
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"
|
die "Pairing address and code are required"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Pairing with device at $pair_address..."
|
log "Pairing with device at $pair_address..."
|
||||||
if adb pair "$pair_address" "$pair_code"; then
|
if adb pair "$pair_address" "$pair_code"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Pairing successful!"
|
echo "✓ Pairing successful!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Now get the connection address:"
|
echo "Now get the connection address:"
|
||||||
echo " On phone: Wireless debugging screen shows IP:port under 'IP address & Port'"
|
echo " On phone: Wireless debugging screen shows IP:port under 'IP address & Port'"
|
||||||
echo " (This is DIFFERENT from the pairing port)"
|
echo " (This is DIFFERENT from the pairing port)"
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
|
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
|
# Save for future connections
|
||||||
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
|
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
|
||||||
echo "$connect_address" >"$WIRELESS_CONFIG"
|
echo "$connect_address" > "$WIRELESS_CONFIG"
|
||||||
log "Saved connection address for future use"
|
log "Saved connection address for future use"
|
||||||
|
|
||||||
# Connect now
|
# Connect now
|
||||||
cmd_connect
|
cmd_connect
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
die "Pairing failed. Make sure the code is correct and you're on the same network."
|
die "Pairing failed. Make sure the code is correct and you're on the same network."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Connect to already-paired device
|
# Connect to already-paired device
|
||||||
cmd_connect() {
|
cmd_connect() {
|
||||||
ensure_adb_installed
|
ensure_adb_installed
|
||||||
|
|
||||||
local connect_address=""
|
local connect_address=""
|
||||||
|
|
||||||
# Check for saved address
|
# Check for saved address
|
||||||
if [[ -f "$WIRELESS_CONFIG" ]]; then
|
if [[ -f $WIRELESS_CONFIG ]]; then
|
||||||
connect_address=$(cat "$WIRELESS_CONFIG")
|
connect_address=$(cat "$WIRELESS_CONFIG")
|
||||||
log "Using saved address: $connect_address"
|
log "Using saved address: $connect_address"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try auto-discovery if no saved address
|
# Try auto-discovery if no saved address
|
||||||
if [[ -z "$connect_address" ]]; then
|
if [[ -z $connect_address ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
log "Searching for Android devices on network..."
|
log "Searching for Android devices on network..."
|
||||||
connect_address=$(discover_android_device)
|
connect_address=$(discover_android_device)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Manual fallback
|
# Manual fallback
|
||||||
if [[ -z "$connect_address" ]]; then
|
if [[ -z $connect_address ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Auto-discovery failed. Enter address manually."
|
echo "Auto-discovery failed. Enter address manually."
|
||||||
echo "On phone: Settings > Developer Options > Wireless debugging"
|
echo "On phone: Settings > Developer Options > Wireless debugging"
|
||||||
echo "Look for IP address & Port (NOT the pairing port)"
|
echo "Look for IP address & Port (NOT the pairing port)"
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
|
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"
|
die "Connection address is required"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Save for future
|
# Save for future
|
||||||
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
|
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
|
||||||
echo "$connect_address" >"$WIRELESS_CONFIG"
|
echo "$connect_address" > "$WIRELESS_CONFIG"
|
||||||
|
|
||||||
log "Connecting to $connect_address..."
|
log "Connecting to $connect_address..."
|
||||||
if adb connect "$connect_address" | grep -q "connected"; then
|
if adb connect "$connect_address" | grep -q "connected"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Connected to device wirelessly!"
|
echo "✓ Connected to device wirelessly!"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Verify connection
|
# Verify connection
|
||||||
if adb devices | grep -q "$connect_address"; then
|
if adb devices | grep -q "$connect_address"; then
|
||||||
echo "Device ready. You can now run other commands."
|
echo "Device ready. You can now run other commands."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo "Connection failed. Possible issues:"
|
echo "Connection failed. Possible issues:"
|
||||||
echo " - Wireless debugging not enabled on phone"
|
echo " - Wireless debugging not enabled on phone"
|
||||||
echo " - Phone and PC not on same WiFi network"
|
echo " - Phone and PC not on same WiFi network"
|
||||||
echo " - Port changed (check Wireless debugging screen)"
|
echo " - Port changed (check Wireless debugging screen)"
|
||||||
echo " - May need to pair first: $0 pair"
|
echo " - May need to pair first: $0 pair"
|
||||||
echo ""
|
echo ""
|
||||||
# Clear saved config since it failed
|
# Clear saved config since it failed
|
||||||
rm -f "$WIRELESS_CONFIG"
|
rm -f "$WIRELESS_CONFIG"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disconnect wireless ADB
|
# Disconnect wireless ADB
|
||||||
cmd_disconnect() {
|
cmd_disconnect() {
|
||||||
ensure_adb_installed
|
ensure_adb_installed
|
||||||
|
|
||||||
log "Disconnecting all wireless devices..."
|
log "Disconnecting all wireless devices..."
|
||||||
adb disconnect
|
adb disconnect
|
||||||
echo "✓ Disconnected"
|
echo "✓ Disconnected"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check device connection and root
|
# Check device connection and root
|
||||||
ensure_device_ready() {
|
ensure_device_ready() {
|
||||||
ensure_adb_installed
|
ensure_adb_installed
|
||||||
|
|
||||||
# Check if any device is connected
|
# Check if any device is connected
|
||||||
if ! adb devices | grep -qE "device$|:.*device$"; then
|
if ! adb devices | grep -qE "device$|:.*device$"; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "No device connected!"
|
echo "No device connected!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " 1. Connect USB cable with debugging enabled"
|
echo " 1. Connect USB cable with debugging enabled"
|
||||||
echo " 2. Use wireless: $0 pair (first time) or $0 connect"
|
echo " 2. Use wireless: $0 pair (first time) or $0 connect"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if we have a saved wireless config
|
# 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
|
read -rp "Try connecting to saved wireless device? [Y/n]: " try_wireless
|
||||||
if [[ "${try_wireless,,}" != "n" ]]; then
|
if [[ ${try_wireless,,} != "n" ]]; then
|
||||||
cmd_connect
|
cmd_connect
|
||||||
else
|
else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
check_adb_device
|
check_adb_device
|
||||||
check_adb_root
|
check_adb_root
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build the module zip
|
# Build the module zip
|
||||||
build_module() {
|
build_module() {
|
||||||
local tmp_dir="$WORK_DIR/guardian_module"
|
local tmp_dir="$WORK_DIR/guardian_module"
|
||||||
local module_zip="$WORK_DIR/android_guardian.zip"
|
local module_zip="$WORK_DIR/android_guardian.zip"
|
||||||
|
|
||||||
echo "[BUILD] Building Android Guardian module..." >&2
|
echo "[BUILD] Building Android Guardian module..." >&2
|
||||||
|
|
||||||
rm -rf "$tmp_dir"
|
rm -rf "$tmp_dir"
|
||||||
mkdir -p "$tmp_dir/system/etc"
|
mkdir -p "$tmp_dir/system/etc"
|
||||||
|
|
||||||
# Copy module files
|
# Copy module files
|
||||||
cp "$GUARDIAN_MODULE_DIR/module.prop" "$tmp_dir/"
|
cp "$GUARDIAN_MODULE_DIR/module.prop" "$tmp_dir/"
|
||||||
cp "$GUARDIAN_MODULE_DIR/service.sh" "$tmp_dir/"
|
cp "$GUARDIAN_MODULE_DIR/service.sh" "$tmp_dir/"
|
||||||
cp "$GUARDIAN_MODULE_DIR/post-fs-data.sh" "$tmp_dir/"
|
cp "$GUARDIAN_MODULE_DIR/post-fs-data.sh" "$tmp_dir/"
|
||||||
cp "$GUARDIAN_MODULE_DIR/uninstall.sh" "$tmp_dir/"
|
cp "$GUARDIAN_MODULE_DIR/uninstall.sh" "$tmp_dir/"
|
||||||
|
|
||||||
# Build hosts file
|
# Build hosts file
|
||||||
local hosts_file="$tmp_dir/system/etc/hosts"
|
local hosts_file="$tmp_dir/system/etc/hosts"
|
||||||
if [[ -f /etc/hosts.stevenblack ]]; then
|
if [[ -f /etc/hosts.stevenblack ]]; then
|
||||||
echo "[BUILD] Using StevenBlack hosts cache..." >&2
|
echo "[BUILD] Using StevenBlack hosts cache..." >&2
|
||||||
cp /etc/hosts.stevenblack "$hosts_file"
|
cp /etc/hosts.stevenblack "$hosts_file"
|
||||||
elif [[ -f /etc/hosts ]]; then
|
elif [[ -f /etc/hosts ]]; then
|
||||||
echo "[BUILD] Using /etc/hosts..." >&2
|
echo "[BUILD] Using /etc/hosts..." >&2
|
||||||
cp /etc/hosts "$hosts_file"
|
cp /etc/hosts "$hosts_file"
|
||||||
else
|
else
|
||||||
die "No hosts file found"
|
die "No hosts file found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Append custom blocking entries
|
# Append custom blocking entries
|
||||||
cat >>"$hosts_file" <<'CUSTOM_EOF'
|
cat >> "$hosts_file" << 'CUSTOM_EOF'
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# Custom blocking entries - Android Guardian
|
# Custom blocking entries - Android Guardian
|
||||||
@ -382,252 +382,252 @@ build_module() {
|
|||||||
0.0.0.0 www.dominos.com
|
0.0.0.0 www.dominos.com
|
||||||
CUSTOM_EOF
|
CUSTOM_EOF
|
||||||
|
|
||||||
local total_entries
|
local total_entries
|
||||||
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
|
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
|
||||||
echo "[BUILD] Hosts file contains $total_entries blocked domains" >&2
|
echo "[BUILD] Hosts file contains $total_entries blocked domains" >&2
|
||||||
|
|
||||||
# Create zip
|
# 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"
|
echo "$module_zip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install/update the guardian module
|
# Install/update the guardian module
|
||||||
cmd_install() {
|
cmd_install() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
local module_zip
|
local module_zip
|
||||||
module_zip=$(build_module)
|
module_zip=$(build_module)
|
||||||
|
|
||||||
log "Pushing module to device..."
|
log "Pushing module to device..."
|
||||||
adb push "$module_zip" /sdcard/android_guardian.zip || die "Failed to push module"
|
adb push "$module_zip" /sdcard/android_guardian.zip || die "Failed to push module"
|
||||||
|
|
||||||
log "Installing module..."
|
log "Installing module..."
|
||||||
adb shell "su -c 'mkdir -p $MODULE_DEST'" || die "Failed to create module directory"
|
adb shell "su -c 'mkdir -p $MODULE_DEST'" || die "Failed to create module directory"
|
||||||
adb shell "su -c 'cd $MODULE_DEST && unzip -o /sdcard/android_guardian.zip'" || die "Failed to extract module"
|
adb shell "su -c 'cd $MODULE_DEST && unzip -o /sdcard/android_guardian.zip'" || die "Failed to extract module"
|
||||||
adb shell "su -c 'chmod 755 $MODULE_DEST/*.sh'"
|
adb shell "su -c 'chmod 755 $MODULE_DEST/*.sh'"
|
||||||
adb shell "su -c 'rm /sdcard/android_guardian.zip'"
|
adb shell "su -c 'rm /sdcard/android_guardian.zip'"
|
||||||
|
|
||||||
# Set up guardian data directory
|
# Set up guardian data directory
|
||||||
log "Setting up guardian data..."
|
log "Setting up guardian data..."
|
||||||
adb shell "su -c 'mkdir -p $GUARDIAN_DATA_DIR'"
|
adb shell "su -c 'mkdir -p $GUARDIAN_DATA_DIR'"
|
||||||
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'"
|
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'"
|
||||||
|
|
||||||
# Copy blocked apps list
|
# Copy blocked apps list
|
||||||
adb push "$GUARDIAN_MODULE_DIR/blocked_apps.txt" /sdcard/blocked_apps.txt || die "Failed to push blocked apps list"
|
adb push "$GUARDIAN_MODULE_DIR/blocked_apps.txt" /sdcard/blocked_apps.txt || die "Failed to push blocked apps list"
|
||||||
adb shell "su -c 'cp /sdcard/blocked_apps.txt $GUARDIAN_DATA_DIR/blocked_apps.txt'"
|
adb shell "su -c 'cp /sdcard/blocked_apps.txt $GUARDIAN_DATA_DIR/blocked_apps.txt'"
|
||||||
adb shell "su -c 'rm /sdcard/blocked_apps.txt'"
|
adb shell "su -c 'rm /sdcard/blocked_apps.txt'"
|
||||||
|
|
||||||
# Create hosts backup for tamper protection
|
# Create hosts backup for tamper protection
|
||||||
adb shell "su -c 'cp $MODULE_DEST/system/etc/hosts $GUARDIAN_DATA_DIR/hosts.backup'"
|
adb shell "su -c 'cp $MODULE_DEST/system/etc/hosts $GUARDIAN_DATA_DIR/hosts.backup'"
|
||||||
|
|
||||||
# Immediately uninstall any currently installed blocked apps
|
# Immediately uninstall any currently installed blocked apps
|
||||||
log "Checking for blocked apps to remove..."
|
log "Checking for blocked apps to remove..."
|
||||||
uninstall_blocked_apps
|
uninstall_blocked_apps
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " ✓ Android Guardian installed!"
|
echo " ✓ Android Guardian installed!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Features enabled:"
|
echo "Features enabled:"
|
||||||
echo " • Hosts-based ad/tracker blocking"
|
echo " • Hosts-based ad/tracker blocking"
|
||||||
echo " • App installation blocking"
|
echo " • App installation blocking"
|
||||||
echo " • Tamper protection"
|
echo " • Tamper protection"
|
||||||
echo ""
|
echo ""
|
||||||
echo "⚠️ This can ONLY be controlled via ADB:"
|
echo "⚠️ This can ONLY be controlled via ADB:"
|
||||||
echo " Disable: $0 disable"
|
echo " Disable: $0 disable"
|
||||||
echo " Enable: $0 enable"
|
echo " Enable: $0 enable"
|
||||||
echo " Status: $0 status"
|
echo " Status: $0 status"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Reboot your device to activate the module."
|
echo "Reboot your device to activate the module."
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Uninstall currently installed blocked apps
|
# Uninstall currently installed blocked apps
|
||||||
uninstall_blocked_apps() {
|
uninstall_blocked_apps() {
|
||||||
local blocked_apps
|
local blocked_apps
|
||||||
blocked_apps=$(grep -v '^#' "$GUARDIAN_MODULE_DIR/blocked_apps.txt" | grep -v '^$' || true)
|
blocked_apps=$(grep -v '^#' "$GUARDIAN_MODULE_DIR/blocked_apps.txt" | grep -v '^$' || true)
|
||||||
|
|
||||||
for package in $blocked_apps; do
|
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"
|
log "Uninstalling blocked app: $package"
|
||||||
adb shell "pm uninstall $package" 2>/dev/null || true
|
adb shell "pm uninstall $package" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show status
|
# Show status
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Android Guardian Status ==="
|
echo "=== Android Guardian Status ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if module is installed
|
# 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"
|
echo "Module: INSTALLED"
|
||||||
else
|
else
|
||||||
echo "Module: NOT INSTALLED"
|
echo "Module: NOT INSTALLED"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check control status
|
# Check control status
|
||||||
local status
|
local status
|
||||||
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo UNKNOWN'" | tr -d '\r')
|
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo UNKNOWN'" | tr -d '\r')
|
||||||
echo "Status: $status"
|
echo "Status: $status"
|
||||||
|
|
||||||
# Check if module is "disabled" in Magisk UI (should be auto-fixed by watchdog)
|
# Check if module is "disabled" in Magisk UI (should be auto-fixed by watchdog)
|
||||||
local magisk_disabled
|
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)"
|
magisk_disabled="YES (watchdog should fix this)"
|
||||||
else
|
else
|
||||||
magisk_disabled="No"
|
magisk_disabled="No"
|
||||||
fi
|
fi
|
||||||
echo "Magisk UI disabled: $magisk_disabled"
|
echo "Magisk UI disabled: $magisk_disabled"
|
||||||
|
|
||||||
# Check if watchdog is running
|
# Check if watchdog is running
|
||||||
local watchdog_running
|
local watchdog_running
|
||||||
watchdog_running=$(adb shell "su -c 'pgrep -f watchdog.sh 2>/dev/null | wc -l'" | tr -d '\r')
|
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)"
|
echo "Watchdog: RUNNING ($watchdog_running processes)"
|
||||||
else
|
else
|
||||||
echo "Watchdog: NOT RUNNING (reboot phone to start)"
|
echo "Watchdog: NOT RUNNING (reboot phone to start)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check hosts file
|
# Check hosts file
|
||||||
local hosts_entries
|
local hosts_entries
|
||||||
hosts_entries=$(adb shell "su -c 'grep -c \"^0.0.0.0\" /system/etc/hosts 2>/dev/null || echo 0'" | tr -d '\r')
|
hosts_entries=$(adb shell "su -c 'grep -c \"^0.0.0.0\" /system/etc/hosts 2>/dev/null || echo 0'" | tr -d '\r')
|
||||||
echo "Blocked domains: $hosts_entries"
|
echo "Blocked domains: $hosts_entries"
|
||||||
|
|
||||||
# Check blocked apps count
|
# Check blocked apps count
|
||||||
local blocked_count
|
local blocked_count
|
||||||
blocked_count=$(adb shell "su -c 'grep -v \"^#\" $GUARDIAN_DATA_DIR/blocked_apps.txt 2>/dev/null | grep -v \"^$\" | wc -l || echo 0'" | tr -d '\r')
|
blocked_count=$(adb shell "su -c 'grep -v \"^#\" $GUARDIAN_DATA_DIR/blocked_apps.txt 2>/dev/null | grep -v \"^$\" | wc -l || echo 0'" | tr -d '\r')
|
||||||
echo "Blocked app rules: $blocked_count packages"
|
echo "Blocked app rules: $blocked_count packages"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Protection: Module cannot be disabled from Magisk UI"
|
echo "Protection: Module cannot be disabled from Magisk UI"
|
||||||
echo " Only controllable via: $0 disable/enable"
|
echo " Only controllable via: $0 disable/enable"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disable guardian
|
# Disable guardian
|
||||||
cmd_disable() {
|
cmd_disable() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
log "Disabling Android Guardian..."
|
log "Disabling Android Guardian..."
|
||||||
adb shell "su -c 'echo DISABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to disable guardian"
|
adb shell "su -c 'echo DISABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to disable guardian"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Guardian DISABLED"
|
echo "✓ Guardian DISABLED"
|
||||||
echo " Hosts blocking still active until reboot"
|
echo " Hosts blocking still active until reboot"
|
||||||
echo " App blocking service paused"
|
echo " App blocking service paused"
|
||||||
echo ""
|
echo ""
|
||||||
echo "To re-enable: $0 enable"
|
echo "To re-enable: $0 enable"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enable guardian
|
# Enable guardian
|
||||||
cmd_enable() {
|
cmd_enable() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
log "Enabling Android Guardian..."
|
log "Enabling Android Guardian..."
|
||||||
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to enable guardian"
|
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to enable guardian"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Guardian ENABLED"
|
echo "✓ Guardian ENABLED"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Uninstall module
|
# Uninstall module
|
||||||
cmd_uninstall() {
|
cmd_uninstall() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
# Check if disabled first
|
# Check if disabled first
|
||||||
local status
|
local status
|
||||||
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo ENABLED'" | tr -d '\r')
|
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 ""
|
||||||
echo "⚠️ Guardian must be disabled before uninstalling!"
|
echo "⚠️ Guardian must be disabled before uninstalling!"
|
||||||
echo " Run: $0 disable"
|
echo " Run: $0 disable"
|
||||||
echo " Then: $0 uninstall"
|
echo " Then: $0 uninstall"
|
||||||
echo ""
|
echo ""
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Removing Android Guardian..."
|
log "Removing Android Guardian..."
|
||||||
adb shell "su -c 'rm -rf $MODULE_DEST'"
|
adb shell "su -c 'rm -rf $MODULE_DEST'"
|
||||||
adb shell "su -c 'rm -rf $GUARDIAN_DATA_DIR'"
|
adb shell "su -c 'rm -rf $GUARDIAN_DATA_DIR'"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✓ Guardian uninstalled"
|
echo "✓ Guardian uninstalled"
|
||||||
echo " Reboot to remove hosts blocking"
|
echo " Reboot to remove hosts blocking"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show logs
|
# Show logs
|
||||||
cmd_logs() {
|
cmd_logs() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
echo "=== Guardian Logs ==="
|
echo "=== Guardian Logs ==="
|
||||||
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/guardian.log 2>/dev/null || echo \"No logs yet\"'"
|
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/guardian.log 2>/dev/null || echo \"No logs yet\"'"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Block an app
|
# Block an app
|
||||||
cmd_block_app() {
|
cmd_block_app() {
|
||||||
local package="${1:-}"
|
local package="${1:-}"
|
||||||
|
|
||||||
if [[ -z "$package" ]]; then
|
if [[ -z $package ]]; then
|
||||||
echo "Usage: $0 block-app <package.name>"
|
echo "Usage: $0 block-app <package.name>"
|
||||||
echo "Example: $0 block-app com.ubercab.eats"
|
echo "Example: $0 block-app com.ubercab.eats"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
log "Adding $package to block list..."
|
log "Adding $package to block list..."
|
||||||
adb shell "su -c 'echo \"$package\" >> $GUARDIAN_DATA_DIR/blocked_apps.txt'"
|
adb shell "su -c 'echo \"$package\" >> $GUARDIAN_DATA_DIR/blocked_apps.txt'"
|
||||||
|
|
||||||
# Also add to local file
|
# 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
|
# 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..."
|
log "Uninstalling $package..."
|
||||||
adb shell "pm uninstall $package" 2>/dev/null || true
|
adb shell "pm uninstall $package" 2> /dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ $package added to block list"
|
echo "✓ $package added to block list"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unblock an app
|
# Unblock an app
|
||||||
cmd_unblock_app() {
|
cmd_unblock_app() {
|
||||||
local package="${1:-}"
|
local package="${1:-}"
|
||||||
|
|
||||||
if [[ -z "$package" ]]; then
|
if [[ -z $package ]]; then
|
||||||
echo "Usage: $0 unblock-app <package.name>"
|
echo "Usage: $0 unblock-app <package.name>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
log "Removing $package from block list..."
|
log "Removing $package from block list..."
|
||||||
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'"
|
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
|
# 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"
|
echo "✓ $package removed from block list"
|
||||||
}
|
}
|
||||||
|
|
||||||
# List blocked apps
|
# List blocked apps
|
||||||
cmd_list_blocked() {
|
cmd_list_blocked() {
|
||||||
ensure_device_ready
|
ensure_device_ready
|
||||||
|
|
||||||
echo "=== Blocked Apps ==="
|
echo "=== Blocked Apps ==="
|
||||||
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/blocked_apps.txt 2>/dev/null'" | grep -v "^#" | grep -v "^$" || echo "No blocked apps"
|
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/blocked_apps.txt 2>/dev/null'" | grep -v "^#" | grep -v "^$" || echo "No blocked apps"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
@ -638,48 +638,48 @@ COMMAND="${1:-install}"
|
|||||||
shift || true
|
shift || true
|
||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
install)
|
install)
|
||||||
cmd_install
|
cmd_install
|
||||||
;;
|
;;
|
||||||
status)
|
status)
|
||||||
cmd_status
|
cmd_status
|
||||||
;;
|
;;
|
||||||
disable)
|
disable)
|
||||||
cmd_disable
|
cmd_disable
|
||||||
;;
|
;;
|
||||||
enable)
|
enable)
|
||||||
cmd_enable
|
cmd_enable
|
||||||
;;
|
;;
|
||||||
uninstall)
|
uninstall)
|
||||||
cmd_uninstall
|
cmd_uninstall
|
||||||
;;
|
;;
|
||||||
logs)
|
logs)
|
||||||
cmd_logs
|
cmd_logs
|
||||||
;;
|
;;
|
||||||
block-app)
|
block-app)
|
||||||
cmd_block_app "$@"
|
cmd_block_app "$@"
|
||||||
;;
|
;;
|
||||||
unblock-app)
|
unblock-app)
|
||||||
cmd_unblock_app "$@"
|
cmd_unblock_app "$@"
|
||||||
;;
|
;;
|
||||||
list-blocked)
|
list-blocked)
|
||||||
cmd_list_blocked
|
cmd_list_blocked
|
||||||
;;
|
;;
|
||||||
pair)
|
pair)
|
||||||
cmd_pair
|
cmd_pair
|
||||||
;;
|
;;
|
||||||
connect)
|
connect)
|
||||||
cmd_connect
|
cmd_connect
|
||||||
;;
|
;;
|
||||||
disconnect)
|
disconnect)
|
||||||
cmd_disconnect
|
cmd_disconnect
|
||||||
;;
|
;;
|
||||||
-h | --help | help)
|
-h | --help | help)
|
||||||
show_usage
|
show_usage
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown command: $COMMAND"
|
echo "Unknown command: $COMMAND"
|
||||||
show_usage
|
show_usage
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user