pre commit fixes

This commit is contained in:
Krzysztof Rudnicki 2026-02-20 00:21:41 +01:00
parent aa8501cc49
commit 9e4fee54d7
18 changed files with 1508 additions and 1488 deletions

View File

@ -821,7 +821,7 @@ auto_remove_virtualbox_vms() {
echo -e "${RED} Failed to remove: ${vm_name}${NC}" >&2
((++failed))
fi
done <<< "$vm_list"
done <<<"$vm_list"
echo -e "${CYAN}VM removal complete: ${success} removed, ${failed} failed.${NC}" >&2
}

View File

@ -15,29 +15,29 @@ NC='\033[0m' # No Color
# Auto-sudo functionality with confirmation
if [ "$EUID" -ne 0 ]; then
echo -e "${YELLOW}This script requires root privileges to configure VirtualBox VMs.${NC}"
echo -e "${CYAN}Executing with sudo...${NC}"
exec sudo bash "$0" "$@"
echo -e "${YELLOW}This script requires root privileges to configure VirtualBox VMs.${NC}"
echo -e "${CYAN}Executing with sudo...${NC}"
exec sudo bash "$0" "$@"
fi
# Determine the real (non-root) user who invoked this script.
# VBoxManage must run as this user because VMs are registered per-user.
REAL_USER="${SUDO_USER:-$USER}"
if [[ $REAL_USER == "root" ]]; then
echo -e "${RED}Cannot determine the real user. Do not run this script as root directly.${NC}"
echo -e "${RED}Run it as a normal user (it will auto-sudo as needed).${NC}"
exit 1
echo -e "${RED}Cannot determine the real user. Do not run this script as root directly.${NC}"
echo -e "${RED}Run it as a normal user (it will auto-sudo as needed).${NC}"
exit 1
fi
# Check if VBoxManage is available
if ! command -v VBoxManage > /dev/null 2>&1; then
echo -e "${RED}VBoxManage not found. VirtualBox may not be installed.${NC}"
exit 1
if ! command -v VBoxManage >/dev/null 2>&1; then
echo -e "${RED}VBoxManage not found. VirtualBox may not be installed.${NC}"
exit 1
fi
# Run VBoxManage as the real user so it sees their registered VMs
vboxmanage_as_user() {
sudo -u "$REAL_USER" VBoxManage "$@"
sudo -u "$REAL_USER" VBoxManage "$@"
}
# Configuration
@ -46,54 +46,54 @@ HOSTS_ENFORCEMENT_MARKER="/var/lib/vbox-hosts-enforced"
# Get list of all VMs
get_all_vms() {
vboxmanage_as_user list vms | awk -F'"' '{print $2}'
vboxmanage_as_user list vms | awk -F'"' '{print $2}'
}
# Get list of running VMs
get_running_vms() {
vboxmanage_as_user list runningvms | awk -F'"' '{print $2}'
vboxmanage_as_user list runningvms | awk -F'"' '{print $2}'
}
# Configure a VM to use host DNS (NAT network)
configure_vm_dns() {
local vm_name="$1"
echo -e "${BLUE}Configuring DNS for VM: ${vm_name}${NC}"
local vm_name="$1"
echo -e "${BLUE}Configuring DNS for VM: ${vm_name}${NC}"
# Enable DNS proxy for NAT adapter (adapter 1 by default)
# This makes the VM use the host's DNS resolution
vboxmanage_as_user modifyvm "$vm_name" --natdnshostresolver1 on 2>/dev/null || true
vboxmanage_as_user modifyvm "$vm_name" --natdnsproxy1 on 2>/dev/null || true
# Enable DNS proxy for NAT adapter (adapter 1 by default)
# This makes the VM use the host's DNS resolution
vboxmanage_as_user modifyvm "$vm_name" --natdnshostresolver1 on 2>/dev/null || true
vboxmanage_as_user modifyvm "$vm_name" --natdnsproxy1 on 2>/dev/null || true
echo -e "${GREEN}DNS configuration applied to ${vm_name}${NC}"
echo -e "${GREEN}DNS configuration applied to ${vm_name}${NC}"
}
# Add shared folder for /etc directory (read-only)
configure_hosts_shared_folder() {
local vm_name="$1"
echo -e "${BLUE}Setting up /etc/hosts sharing for VM: ${vm_name}${NC}"
local vm_name="$1"
echo -e "${BLUE}Setting up /etc/hosts sharing for VM: ${vm_name}${NC}"
# Remove existing shared folder if present
vboxmanage_as_user sharedfolder remove "$vm_name" --name "$VBOX_SHARED_FOLDER_NAME" 2>/dev/null || true
# Remove existing shared folder if present
vboxmanage_as_user sharedfolder remove "$vm_name" --name "$VBOX_SHARED_FOLDER_NAME" 2>/dev/null || true
# Add /etc as a shared folder (read-only)
vboxmanage_as_user sharedfolder add "$vm_name" \
--name "$VBOX_SHARED_FOLDER_NAME" \
--hostpath "/etc" \
--readonly \
--automount 2>/dev/null || {
echo -e "${YELLOW}Could not add shared folder to ${vm_name} (VM may be running)${NC}"
return 1
}
# Add /etc as a shared folder (read-only)
vboxmanage_as_user sharedfolder add "$vm_name" \
--name "$VBOX_SHARED_FOLDER_NAME" \
--hostpath "/etc" \
--readonly \
--automount 2>/dev/null || {
echo -e "${YELLOW}Could not add shared folder to ${vm_name} (VM may be running)${NC}"
return 1
}
echo -e "${GREEN}Shared folder configured for ${vm_name}${NC}"
return 0
echo -e "${GREEN}Shared folder configured for ${vm_name}${NC}"
return 0
}
# Create a startup script that can be placed in VMs
generate_vm_startup_script() {
local output_file="${1:-/tmp/vbox_hosts_sync.sh}"
local output_file="${1:-/tmp/vbox_hosts_sync.sh}"
cat > "$output_file" << 'EOF'
cat >"$output_file" <<'EOF'
#!/bin/bash
# VirtualBox VM startup script to sync /etc/hosts from host machine
# This should be placed in the VM and run at startup
@ -161,283 +161,283 @@ if [ -f "$HOST_HOSTS_FILE" ]; then
fi
EOF
chmod +x "$output_file"
echo -e "${GREEN}Generated VM startup script at ${output_file}${NC}"
echo -e "${CYAN}Copy this script to your VMs and add it to their startup (e.g., /etc/rc.local or systemd)${NC}"
chmod +x "$output_file"
echo -e "${GREEN}Generated VM startup script at ${output_file}${NC}"
echo -e "${CYAN}Copy this script to your VMs and add it to their startup (e.g., /etc/rc.local or systemd)${NC}"
}
# Get the disk image path for a VM (first SATA/IDE .vdi/.vmdk/.vhd)
get_vm_disk_path() {
local vm_name="$1"
vboxmanage_as_user showvminfo "$vm_name" --machinereadable 2>/dev/null \
| grep -E '^"(SATA|IDE|SCSI|NVMe)-[0-9]+-[0-9]+"=' \
| grep -vE '="none"$' \
| grep -vE '\.iso"$' \
| head -1 \
| sed 's/^[^=]*="//; s/"$//'
local vm_name="$1"
vboxmanage_as_user showvminfo "$vm_name" --machinereadable 2>/dev/null |
grep -E '^"(SATA|IDE|SCSI|NVMe)-[0-9]+-[0-9]+"=' |
grep -vE '="none"$' |
grep -vE '\.iso"$' |
head -1 |
sed 's/^[^=]*="//; s/"$//'
}
# Inject host's /etc/hosts directly into a VM disk image using qemu-nbd.
# This is the only reliable way to enforce blocking, because NAT DNS proxy
# alone does not work when the guest browser uses DNS-over-HTTPS (DoH).
inject_hosts_into_vm_disk() {
local vm_name="$1"
local disk_path
disk_path="$(get_vm_disk_path "$vm_name")"
local vm_name="$1"
local disk_path
disk_path="$(get_vm_disk_path "$vm_name")"
if [[ -z $disk_path || ! -f $disk_path ]]; then
echo -e "${YELLOW}Could not find disk image for VM '${vm_name}', skipping hosts injection${NC}"
return 1
fi
if [[ -z $disk_path || ! -f $disk_path ]]; then
echo -e "${YELLOW}Could not find disk image for VM '${vm_name}', skipping hosts injection${NC}"
return 1
fi
# Ensure VM is not running
if vboxmanage_as_user list runningvms 2>/dev/null | grep -q "\"${vm_name}\""; then
echo -e "${YELLOW}VM '${vm_name}' is running, cannot inject hosts file. Stop it first.${NC}"
return 1
fi
# Ensure VM is not running
if vboxmanage_as_user list runningvms 2>/dev/null | grep -q "\"${vm_name}\""; then
echo -e "${YELLOW}VM '${vm_name}' is running, cannot inject hosts file. Stop it first.${NC}"
return 1
fi
# Check for qemu-nbd
if ! command -v qemu-nbd > /dev/null 2>&1; then
echo -e "${YELLOW}qemu-nbd not found. Install qemu-base to enable hosts file injection.${NC}"
return 1
fi
# Check for qemu-nbd
if ! command -v qemu-nbd >/dev/null 2>&1; then
echo -e "${YELLOW}qemu-nbd not found. Install qemu-base to enable hosts file injection.${NC}"
return 1
fi
echo -e "${BLUE}Injecting /etc/hosts into disk image for VM: ${vm_name}${NC}"
echo -e "${BLUE}Injecting /etc/hosts into disk image for VM: ${vm_name}${NC}"
# Load nbd module if needed
if [[ ! -e /dev/nbd0 ]]; then
modprobe nbd max_part=8 2>/dev/null || {
echo -e "${YELLOW}Could not load nbd kernel module${NC}"
return 1
}
fi
# Load nbd module if needed
if [[ ! -e /dev/nbd0 ]]; then
modprobe nbd max_part=8 2>/dev/null || {
echo -e "${YELLOW}Could not load nbd kernel module${NC}"
return 1
}
fi
# Find a free nbd device
local nbd_dev=""
for dev in /dev/nbd{0..15}; do
if [[ -e $dev ]] && ! lsblk "$dev" > /dev/null 2>&1; then
nbd_dev="$dev"
break
fi
done
# Fallback: try /dev/nbd0 if no device was found via lsblk check
if [[ -z $nbd_dev ]]; then
nbd_dev="/dev/nbd0"
fi
# Find a free nbd device
local nbd_dev=""
for dev in /dev/nbd{0..15}; do
if [[ -e $dev ]] && ! lsblk "$dev" >/dev/null 2>&1; then
nbd_dev="$dev"
break
fi
done
# Fallback: try /dev/nbd0 if no device was found via lsblk check
if [[ -z $nbd_dev ]]; then
nbd_dev="/dev/nbd0"
fi
local mount_point="/tmp/vbox_hosts_inject_$$"
local mount_point="/tmp/vbox_hosts_inject_$$"
# Connect disk image
qemu-nbd --connect="$nbd_dev" "$disk_path" 2>/dev/null || {
echo -e "${YELLOW}Could not connect disk image via qemu-nbd${NC}"
return 1
}
# Connect disk image
qemu-nbd --connect="$nbd_dev" "$disk_path" 2>/dev/null || {
echo -e "${YELLOW}Could not connect disk image via qemu-nbd${NC}"
return 1
}
# Wait for partitions to appear
sleep 1
partprobe "$nbd_dev" 2>/dev/null || true
# Wait for partitions to appear
sleep 1
partprobe "$nbd_dev" 2>/dev/null || true
# Find the root partition (first Linux partition)
local part=""
for p in "${nbd_dev}p1" "${nbd_dev}p2" "${nbd_dev}p3"; do
if [[ -b $p ]]; then
part="$p"
break
fi
done
# Find the root partition (first Linux partition)
local part=""
for p in "${nbd_dev}p1" "${nbd_dev}p2" "${nbd_dev}p3"; do
if [[ -b $p ]]; then
part="$p"
break
fi
done
if [[ -z $part ]]; then
echo -e "${YELLOW}No partitions found on disk image${NC}"
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
return 1
fi
if [[ -z $part ]]; then
echo -e "${YELLOW}No partitions found on disk image${NC}"
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
return 1
fi
# Mount the partition
mkdir -p "$mount_point"
if ! mount "$part" "$mount_point" 2>/dev/null; then
# Journal may need recovery — run e2fsck then retry
e2fsck -y "$part" > /dev/null 2>&1 || true
if ! mount "$part" "$mount_point" 2>/dev/null; then
echo -e "${YELLOW}Could not mount partition $part${NC}"
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
return 1
fi
fi
# Mount the partition
mkdir -p "$mount_point"
if ! mount "$part" "$mount_point" 2>/dev/null; then
# Journal may need recovery — run e2fsck then retry
e2fsck -y "$part" >/dev/null 2>&1 || true
if ! mount "$part" "$mount_point" 2>/dev/null; then
echo -e "${YELLOW}Could not mount partition $part${NC}"
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
return 1
fi
fi
# Check if this partition has /etc/hosts (i.e., it's the root fs)
if [[ ! -f "$mount_point/etc/hosts" ]]; then
echo -e "${YELLOW}Partition does not appear to be root filesystem (no /etc/hosts)${NC}"
umount "$mount_point" 2>/dev/null || true
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
return 1
fi
# Check if this partition has /etc/hosts (i.e., it's the root fs)
if [[ ! -f "$mount_point/etc/hosts" ]]; then
echo -e "${YELLOW}Partition does not appear to be root filesystem (no /etc/hosts)${NC}"
umount "$mount_point" 2>/dev/null || true
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
return 1
fi
# Backup original if not already backed up
if [[ ! -f "$mount_point/etc/hosts.original" ]]; then
cp "$mount_point/etc/hosts" "$mount_point/etc/hosts.original"
echo -e "${CYAN}Backed up original hosts file${NC}"
fi
# Backup original if not already backed up
if [[ ! -f "$mount_point/etc/hosts.original" ]]; then
cp "$mount_point/etc/hosts" "$mount_point/etc/hosts.original"
echo -e "${CYAN}Backed up original hosts file${NC}"
fi
# Copy host's /etc/hosts into VM
cp /etc/hosts "$mount_point/etc/hosts"
chmod 444 "$mount_point/etc/hosts"
# Copy host's /etc/hosts into VM
cp /etc/hosts "$mount_point/etc/hosts"
chmod 444 "$mount_point/etc/hosts"
local blocked_count
blocked_count="$(grep -c '0.0.0.0' "$mount_point/etc/hosts")"
local blocked_count
blocked_count="$(grep -c '0.0.0.0' "$mount_point/etc/hosts")"
# Cleanup: unmount and disconnect
umount "$mount_point" 2>/dev/null || true
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
# Cleanup: unmount and disconnect
umount "$mount_point" 2>/dev/null || true
qemu-nbd --disconnect "$nbd_dev" 2>/dev/null || true
rmdir "$mount_point" 2>/dev/null || true
echo -e "${GREEN}Hosts file injected into VM '${vm_name}' (${blocked_count} domains blocked)${NC}"
return 0
echo -e "${GREEN}Hosts file injected into VM '${vm_name}' (${blocked_count} domains blocked)${NC}"
return 0
}
# Apply enforcement to all VMs
enforce_all_vms() {
local -a vms
mapfile -t vms < <(get_all_vms)
local -a vms
mapfile -t vms < <(get_all_vms)
if [[ ${#vms[@]} -eq 0 ]]; then
echo -e "${YELLOW}No VirtualBox VMs found.${NC}"
return 0
fi
if [[ ${#vms[@]} -eq 0 ]]; then
echo -e "${YELLOW}No VirtualBox VMs found.${NC}"
return 0
fi
echo -e "${CYAN}Found ${#vms[@]} VM(s). Applying /etc/hosts enforcement...${NC}"
echo -e "${CYAN}Found ${#vms[@]} VM(s). Applying /etc/hosts enforcement...${NC}"
local success=0
local failed=0
local success=0
local failed=0
for vm in "${vms[@]}"; do
echo -e "\n${BLUE}Processing VM: ${vm}${NC}"
for vm in "${vms[@]}"; do
echo -e "\n${BLUE}Processing VM: ${vm}${NC}"
# Configure DNS settings (works even when VM is running)
configure_vm_dns "$vm"
# Configure DNS settings (works even when VM is running)
configure_vm_dns "$vm"
# Try to configure shared folder (only works when VM is stopped)
if configure_hosts_shared_folder "$vm"; then
((++success))
else
((++failed))
echo -e "${YELLOW}Note: Stop the VM and run this script again to add shared folder${NC}"
fi
# Try to configure shared folder (only works when VM is stopped)
if configure_hosts_shared_folder "$vm"; then
((++success))
else
((++failed))
echo -e "${YELLOW}Note: Stop the VM and run this script again to add shared folder${NC}"
fi
# Inject hosts file directly into VM disk (the actual enforcement)
inject_hosts_into_vm_disk "$vm" || true
done
# Inject hosts file directly into VM disk (the actual enforcement)
inject_hosts_into_vm_disk "$vm" || true
done
echo -e "\n${GREEN}Enforcement complete!${NC}"
echo -e "Successfully configured: ${success} VM(s)"
[[ $failed -gt 0 ]] && echo -e "${YELLOW}Needs VM shutdown for full config: ${failed} VM(s)${NC}"
echo -e "\n${GREEN}Enforcement complete!${NC}"
echo -e "Successfully configured: ${success} VM(s)"
[[ $failed -gt 0 ]] && echo -e "${YELLOW}Needs VM shutdown for full config: ${failed} VM(s)${NC}"
# Mark that enforcement has been applied
touch "$HOSTS_ENFORCEMENT_MARKER"
# Mark that enforcement has been applied
touch "$HOSTS_ENFORCEMENT_MARKER"
}
# Check if a single VM has the shared folder configured
vm_has_shared_folder() {
local vm_name="$1"
vboxmanage_as_user showvminfo "$vm_name" --machinereadable 2>/dev/null \
| grep -q "SharedFolderNameMachineMapping.*=\"${VBOX_SHARED_FOLDER_NAME}\""
local vm_name="$1"
vboxmanage_as_user showvminfo "$vm_name" --machinereadable 2>/dev/null |
grep -q "SharedFolderNameMachineMapping.*=\"${VBOX_SHARED_FOLDER_NAME}\""
}
# Check if enforcement is applied to ALL registered VMs
check_enforcement_status() {
local -a vms
mapfile -t vms < <(get_all_vms)
local -a vms
mapfile -t vms < <(get_all_vms)
if [[ ${#vms[@]} -eq 0 ]]; then
echo -e "${GREEN}No VMs to enforce.${NC}"
return 0
fi
if [[ ${#vms[@]} -eq 0 ]]; then
echo -e "${GREEN}No VMs to enforce.${NC}"
return 0
fi
for vm in "${vms[@]}"; do
if ! vm_has_shared_folder "$vm"; then
echo -e "${YELLOW}VM '${vm}' is missing hosts enforcement.${NC}"
return 1
fi
done
for vm in "${vms[@]}"; do
if ! vm_has_shared_folder "$vm"; then
echo -e "${YELLOW}VM '${vm}' is missing hosts enforcement.${NC}"
return 1
fi
done
echo -e "${GREEN}All ${#vms[@]} VM(s) have hosts enforcement applied.${NC}"
return 0
echo -e "${GREEN}All ${#vms[@]} VM(s) have hosts enforcement applied.${NC}"
return 0
}
# Show status
show_status() {
echo -e "${CYAN}VirtualBox Hosts Enforcement Status${NC}"
echo -e "${CYAN}====================================${NC}\n"
echo -e "${CYAN}VirtualBox Hosts Enforcement Status${NC}"
echo -e "${CYAN}====================================${NC}\n"
local -a all_vms running_vms
mapfile -t all_vms < <(get_all_vms)
mapfile -t running_vms < <(get_running_vms)
local -a all_vms running_vms
mapfile -t all_vms < <(get_all_vms)
mapfile -t running_vms < <(get_running_vms)
echo -e "Total VMs: ${#all_vms[@]}"
echo -e "Running VMs: ${#running_vms[@]}"
echo -e "Total VMs: ${#all_vms[@]}"
echo -e "Running VMs: ${#running_vms[@]}"
if check_enforcement_status > /dev/null 2>&1; then
echo -e "Enforcement status: ${GREEN}Applied to all VMs${NC}"
else
echo -e "Enforcement status: ${RED}Not fully applied${NC}"
fi
if check_enforcement_status >/dev/null 2>&1; then
echo -e "Enforcement status: ${GREEN}Applied to all VMs${NC}"
else
echo -e "Enforcement status: ${RED}Not fully applied${NC}"
fi
echo -e "\n${CYAN}VMs:${NC}"
for vm in "${all_vms[@]}"; do
local flags=""
if printf '%s\n' "${running_vms[@]}" | grep -qx "$vm"; then
flags+=" ${GREEN}[RUNNING]${NC}"
fi
if vm_has_shared_folder "$vm"; then
flags+=" ${GREEN}[ENFORCED]${NC}"
else
flags+=" ${RED}[NOT ENFORCED]${NC}"
fi
echo -e " - ${vm}${flags}"
done
echo -e "\n${CYAN}VMs:${NC}"
for vm in "${all_vms[@]}"; do
local flags=""
if printf '%s\n' "${running_vms[@]}" | grep -qx "$vm"; then
flags+=" ${GREEN}[RUNNING]${NC}"
fi
if vm_has_shared_folder "$vm"; then
flags+=" ${GREEN}[ENFORCED]${NC}"
else
flags+=" ${RED}[NOT ENFORCED]${NC}"
fi
echo -e " - ${vm}${flags}"
done
}
# Main function
main() {
local action="${1:-enforce}"
local action="${1:-enforce}"
case "$action" in
enforce|apply)
enforce_all_vms
;;
check)
if check_enforcement_status; then
exit 0
else
exit 1
fi
;;
status)
show_status
;;
generate-script)
local output="${2:-/tmp/vbox_hosts_sync.sh}"
generate_vm_startup_script "$output"
;;
*)
echo -e "${CYAN}VirtualBox /etc/hosts Enforcement Tool${NC}"
echo ""
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " enforce Apply /etc/hosts enforcement to all VMs (default)"
echo " check Check if enforcement has been applied"
echo " status Show current enforcement status"
echo " generate-script [path] Generate a script to place in VMs for hosts sync"
echo ""
echo "This tool configures VirtualBox VMs to:"
echo " 1. Use host's DNS resolution (via NAT DNS proxy)"
echo " 2. Share /etc from host (read-only) for hosts file access"
echo ""
exit 0
;;
esac
case "$action" in
enforce | apply)
enforce_all_vms
;;
check)
if check_enforcement_status; then
exit 0
else
exit 1
fi
;;
status)
show_status
;;
generate-script)
local output="${2:-/tmp/vbox_hosts_sync.sh}"
generate_vm_startup_script "$output"
;;
*)
echo -e "${CYAN}VirtualBox /etc/hosts Enforcement Tool${NC}"
echo ""
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " enforce Apply /etc/hosts enforcement to all VMs (default)"
echo " check Check if enforcement has been applied"
echo " status Show current enforcement status"
echo " generate-script [path] Generate a script to place in VMs for hosts sync"
echo ""
echo "This tool configures VirtualBox VMs to:"
echo " 1. Use host's DNS resolution (via NAT DNS proxy)"
echo " 2. Share /etc from host (read-only) for hosts file access"
echo ""
exit 0
;;
esac
}
main "$@"

View File

@ -15,7 +15,7 @@ declare -a FINDINGS=()
declare -a ACTIONS=()
usage() {
cat << 'EOF'
cat <<'EOF'
diagnose_arch_performance.sh - Diagnose common causes of Arch Linux slowness/instability
Usage:
@ -38,334 +38,334 @@ EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--apply-safe-fixes)
APPLY_SAFE_FIXES=true
shift
;;
--install-tools)
INSTALL_TOOLS=true
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 2
;;
esac
done
while [[ $# -gt 0 ]]; do
case "$1" in
--apply-safe-fixes)
APPLY_SAFE_FIXES=true
shift
;;
--install-tools)
INSTALL_TOOLS=true
shift
;;
-h | --help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 2
;;
esac
done
}
add_finding() {
FINDINGS+=("$1")
log_warn "$1"
FINDINGS+=("$1")
log_warn "$1"
}
add_action() {
ACTIONS+=("$1")
log_info "$1"
ACTIONS+=("$1")
log_info "$1"
}
run_and_log() {
local header="$1"
shift
{
echo
echo "=== $header ==="
"$@" 2>&1 || true
} >> "$REPORT_FILE"
local header="$1"
shift
{
echo
echo "=== $header ==="
"$@" 2>&1 || true
} >>"$REPORT_FILE"
}
check_root_if_needed() {
if [[ $APPLY_SAFE_FIXES == "true" || $INSTALL_TOOLS == "true" ]]; then
require_root "$@"
fi
if [[ $APPLY_SAFE_FIXES == "true" || $INSTALL_TOOLS == "true" ]]; then
require_root "$@"
fi
}
install_optional_tools() {
if [[ $INSTALL_TOOLS != "true" ]]; then
return
fi
if [[ $INSTALL_TOOLS != "true" ]]; then
return
fi
local packages=(lm_sensors smartmontools nvtop iotop powertop)
log_info "Installing optional diagnostic packages: ${packages[*]}"
pacman -S --needed --noconfirm "${packages[@]}"
local packages=(lm_sensors smartmontools nvtop iotop powertop)
log_info "Installing optional diagnostic packages: ${packages[*]}"
pacman -S --needed --noconfirm "${packages[@]}"
}
collect_basics() {
run_and_log "Kernel" uname -a
run_and_log "Uptime" uptime
run_and_log "Memory" free -h
run_and_log "Swap" swapon --show
run_and_log "CPU (lscpu)" lscpu
run_and_log "Disk Usage" df -h /
run_and_log "Boot Time" systemd-analyze
run_and_log "Failed Units" systemctl --failed --no-pager
run_and_log "Recent Errors (this boot)" journalctl -b -p err --no-pager -n 200
run_and_log "Kernel" uname -a
run_and_log "Uptime" uptime
run_and_log "Memory" free -h
run_and_log "Swap" swapon --show
run_and_log "CPU (lscpu)" lscpu
run_and_log "Disk Usage" df -h /
run_and_log "Boot Time" systemd-analyze
run_and_log "Failed Units" systemctl --failed --no-pager
run_and_log "Recent Errors (this boot)" journalctl -b -p err --no-pager -n 200
local cpu_count
cpu_count=$(getconf _NPROCESSORS_ONLN 2> /dev/null || echo 1)
local load1
load1=$(awk '{print int($1)}' /proc/loadavg 2> /dev/null || echo 0)
if [[ ${load1:-0} -ge ${cpu_count:-1} ]]; then
add_finding "1-minute load average is at/above CPU thread count (${load1}/${cpu_count}); background tasks may be saturating the system."
fi
local cpu_count
cpu_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1)
local load1
load1=$(awk '{print int($1)}' /proc/loadavg 2>/dev/null || echo 0)
if [[ ${load1:-0} -ge ${cpu_count:-1} ]]; then
add_finding "1-minute load average is at/above CPU thread count (${load1}/${cpu_count}); background tasks may be saturating the system."
fi
local failed_count
failed_count=$(systemctl --failed --no-legend 2> /dev/null | wc -l || true)
failed_count=${failed_count//[[:space:]]/}
if [[ ${failed_count:-0} -gt 0 ]]; then
add_finding "One or more systemd units are failed (${failed_count}); failed services can cause repeated retries and instability."
fi
local failed_count
failed_count=$(systemctl --failed --no-legend 2>/dev/null | wc -l || true)
failed_count=${failed_count//[[:space:]]/}
if [[ ${failed_count:-0} -gt 0 ]]; then
add_finding "One or more systemd units are failed (${failed_count}); failed services can cause repeated retries and instability."
fi
local acpi_error_count
acpi_error_count=$(journalctl -b -p err --no-pager 2> /dev/null | grep -ic 'acpi' || true)
if [[ ${acpi_error_count:-0} -ge 5 ]]; then
add_finding "Frequent ACPI errors detected in current boot (${acpi_error_count}); BIOS/firmware update may improve stability."
fi
local acpi_error_count
acpi_error_count=$(journalctl -b -p err --no-pager 2>/dev/null | grep -ic 'acpi' || true)
if [[ ${acpi_error_count:-0} -ge 5 ]]; then
add_finding "Frequent ACPI errors detected in current boot (${acpi_error_count}); BIOS/firmware update may improve stability."
fi
local top_snapshot
top_snapshot=$(ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 12 || true)
{
echo
echo "=== Top CPU Processes ==="
echo "$top_snapshot"
} >> "$REPORT_FILE"
local top_snapshot
top_snapshot=$(ps -eo pid,comm,%cpu,%mem --sort=-%cpu | head -n 12 || true)
{
echo
echo "=== Top CPU Processes ==="
echo "$top_snapshot"
} >>"$REPORT_FILE"
local xorg_cpu
xorg_cpu=$(ps -C Xorg -o %cpu= | awk '{sum+=$1} END {printf "%.0f", sum+0}' || echo 0)
if [[ ${xorg_cpu:-0} -ge 20 ]]; then
add_finding "Xorg is using high CPU (${xorg_cpu}%); desktop/compositor/GPU driver path may be a primary slowdown source."
fi
local xorg_cpu
xorg_cpu=$(ps -C Xorg -o %cpu= | awk '{sum+=$1} END {printf "%.0f", sum+0}' || echo 0)
if [[ ${xorg_cpu:-0} -ge 20 ]]; then
add_finding "Xorg is using high CPU (${xorg_cpu}%); desktop/compositor/GPU driver path may be a primary slowdown source."
fi
}
check_cpu_governor() {
local gov_files
gov_files=$(find /sys/devices/system/cpu -maxdepth 3 -name scaling_governor 2> /dev/null || true)
local gov_files
gov_files=$(find /sys/devices/system/cpu -maxdepth 3 -name scaling_governor 2>/dev/null || true)
if [[ -z $gov_files ]]; then
add_action "CPU governor files not found (may be unsupported on this platform)."
return
fi
if [[ -z $gov_files ]]; then
add_action "CPU governor files not found (may be unsupported on this platform)."
return
fi
local summary
summary=$(awk '{count[$1]++} END {for (g in count) printf "%s:%d ", g, count[g]}' $gov_files 2> /dev/null || true)
echo "CPU governor summary: ${summary:-unknown}" >> "$REPORT_FILE"
local summary
summary=$(awk '{count[$1]++} END {for (g in count) printf "%s:%d ", g, count[g]}' $gov_files 2>/dev/null || true)
echo "CPU governor summary: ${summary:-unknown}" >>"$REPORT_FILE"
if grep -q '^powersave$' $gov_files 2> /dev/null; then
add_finding "CPU governor includes 'powersave' on one or more cores; this can make high-end hardware feel slow."
fi
if grep -q '^powersave$' $gov_files 2>/dev/null; then
add_finding "CPU governor includes 'powersave' on one or more cores; this can make high-end hardware feel slow."
fi
}
check_thermal_state() {
if has_cmd sensors; then
run_and_log "Temperatures (sensors)" sensors
else
add_action "Install lm_sensors and run 'sensors' to verify thermal throttling."
fi
if has_cmd sensors; then
run_and_log "Temperatures (sensors)" sensors
else
add_action "Install lm_sensors and run 'sensors' to verify thermal throttling."
fi
if has_cmd dmesg; then
local therm_hits
therm_hits=$(dmesg | grep -Ei 'throttl|thermal|overheat|cpu clock throttled' | tail -n 30 || true)
if [[ -n $therm_hits ]]; then
add_finding "Kernel logs show thermal/throttling related messages."
{
echo
echo "=== Thermal/Throttling dmesg excerpts ==="
echo "$therm_hits"
} >> "$REPORT_FILE"
fi
fi
if has_cmd dmesg; then
local therm_hits
therm_hits=$(dmesg | grep -Ei 'throttl|thermal|overheat|cpu clock throttled' | tail -n 30 || true)
if [[ -n $therm_hits ]]; then
add_finding "Kernel logs show thermal/throttling related messages."
{
echo
echo "=== Thermal/Throttling dmesg excerpts ==="
echo "$therm_hits"
} >>"$REPORT_FILE"
fi
fi
}
check_power_services() {
local tlp_enabled="false"
local ppd_enabled="false"
local tlp_enabled="false"
local ppd_enabled="false"
if systemctl is-enabled tlp.service > /dev/null 2>&1; then
tlp_enabled="true"
fi
if systemctl is-enabled power-profiles-daemon.service > /dev/null 2>&1; then
ppd_enabled="true"
fi
if systemctl is-enabled tlp.service >/dev/null 2>&1; then
tlp_enabled="true"
fi
if systemctl is-enabled power-profiles-daemon.service >/dev/null 2>&1; then
ppd_enabled="true"
fi
echo "Power services: tlp=${tlp_enabled}, power-profiles-daemon=${ppd_enabled}" >> "$REPORT_FILE"
echo "Power services: tlp=${tlp_enabled}, power-profiles-daemon=${ppd_enabled}" >>"$REPORT_FILE"
if [[ $tlp_enabled == "true" && $ppd_enabled == "true" ]]; then
add_finding "Both TLP and power-profiles-daemon are enabled; they often conflict and cause inconsistent performance."
fi
if [[ $tlp_enabled == "true" && $ppd_enabled == "true" ]]; then
add_finding "Both TLP and power-profiles-daemon are enabled; they often conflict and cause inconsistent performance."
fi
if [[ $tlp_enabled == "false" && $ppd_enabled == "false" ]]; then
add_action "No power management daemon is enabled; consider installing/enabling power-profiles-daemon for predictable AC/battery behavior."
fi
if [[ $tlp_enabled == "false" && $ppd_enabled == "false" ]]; then
add_action "No power management daemon is enabled; consider installing/enabling power-profiles-daemon for predictable AC/battery behavior."
fi
}
check_storage_health() {
run_and_log "Block Devices" lsblk -o NAME,MODEL,ROTA,SIZE,TYPE,MOUNTPOINT,FSTYPE
run_and_log "Block Devices" lsblk -o NAME,MODEL,ROTA,SIZE,TYPE,MOUNTPOINT,FSTYPE
if has_cmd fstrim; then
run_and_log "fstrim dry-run" fstrim -av --dry-run
fi
if has_cmd fstrim; then
run_and_log "fstrim dry-run" fstrim -av --dry-run
fi
if systemctl is-enabled fstrim.timer > /dev/null 2>&1; then
add_action "fstrim.timer is enabled (good for SSD performance longevity)."
else
add_finding "fstrim.timer is not enabled; SSD maintenance trimming may be missing."
fi
if systemctl is-enabled fstrim.timer >/dev/null 2>&1; then
add_action "fstrim.timer is enabled (good for SSD performance longevity)."
else
add_finding "fstrim.timer is not enabled; SSD maintenance trimming may be missing."
fi
if has_cmd smartctl; then
local root_disk
root_disk=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p$//' || true)
if [[ -n ${root_disk:-} && -b $root_disk ]]; then
run_and_log "SMART Summary ($root_disk)" smartctl -H "$root_disk"
fi
else
add_action "Install smartmontools and run SMART health checks for your SSD/NVMe."
fi
if has_cmd smartctl; then
local root_disk
root_disk=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p$//' || true)
if [[ -n ${root_disk:-} && -b $root_disk ]]; then
run_and_log "SMART Summary ($root_disk)" smartctl -H "$root_disk"
fi
else
add_action "Install smartmontools and run SMART health checks for your SSD/NVMe."
fi
}
check_memory_pressure() {
local mem_total mem_available swap_total swap_free
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo)
swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo)
local mem_total mem_available swap_total swap_free
mem_total=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_available=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo)
swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo)
if [[ ${swap_total:-0} -gt 0 ]]; then
local swap_used
swap_used=$((swap_total - swap_free))
local swap_pct
swap_pct=$((swap_used * 100 / swap_total))
echo "Swap usage: ${swap_pct}%" >> "$REPORT_FILE"
if [[ $swap_pct -ge 35 && ${mem_available:-0} -gt $((mem_total / 3)) ]]; then
add_finding "High swap usage while RAM is still available; this can cause stutter."
add_action "Consider lowering swappiness (temporary: sudo sysctl vm.swappiness=10)."
fi
fi
if [[ ${swap_total:-0} -gt 0 ]]; then
local swap_used
swap_used=$((swap_total - swap_free))
local swap_pct
swap_pct=$((swap_used * 100 / swap_total))
echo "Swap usage: ${swap_pct}%" >>"$REPORT_FILE"
if [[ $swap_pct -ge 35 && ${mem_available:-0} -gt $((mem_total / 3)) ]]; then
add_finding "High swap usage while RAM is still available; this can cause stutter."
add_action "Consider lowering swappiness (temporary: sudo sysctl vm.swappiness=10)."
fi
fi
if [[ -f /proc/pressure/memory ]]; then
run_and_log "Memory PSI" cat /proc/pressure/memory
fi
if [[ -f /proc/pressure/memory ]]; then
run_and_log "Memory PSI" cat /proc/pressure/memory
fi
}
check_gpu_state() {
if has_cmd nvidia-smi; then
run_and_log "NVIDIA State" nvidia-smi
local pstate util power
pstate=$(nvidia-smi --query-gpu=pstate --format=csv,noheader 2> /dev/null | head -n 1 | xargs || true)
util=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2> /dev/null | head -n 1 | xargs || true)
power=$(nvidia-smi --query-gpu=power.draw --format=csv,noheader,nounits 2> /dev/null | head -n 1 | xargs || true)
if has_cmd nvidia-smi; then
run_and_log "NVIDIA State" nvidia-smi
local pstate util power
pstate=$(nvidia-smi --query-gpu=pstate --format=csv,noheader 2>/dev/null | head -n 1 | xargs || true)
util=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null | head -n 1 | xargs || true)
power=$(nvidia-smi --query-gpu=power.draw --format=csv,noheader,nounits 2>/dev/null | head -n 1 | xargs || true)
echo "NVIDIA pstate: ${pstate:-unknown}" >> "$REPORT_FILE"
echo "NVIDIA util: ${util:-unknown}%" >> "$REPORT_FILE"
echo "NVIDIA power: ${power:-unknown}W" >> "$REPORT_FILE"
echo "NVIDIA pstate: ${pstate:-unknown}" >>"$REPORT_FILE"
echo "NVIDIA util: ${util:-unknown}%" >>"$REPORT_FILE"
echo "NVIDIA power: ${power:-unknown}W" >>"$REPORT_FILE"
if [[ ${pstate:-} == "P0" && ${util:-100} -le 5 ]]; then
add_finding "NVIDIA GPU is in P0 high-performance state while mostly idle; this can increase heat and trigger thermal limits."
add_action "If laptop has hybrid graphics, prefer iGPU mode for desktop workloads and use dGPU on demand."
fi
else
run_and_log "PCI VGA Devices" lspci -nnk | grep -A3 -Ei 'vga|3d|display'
fi
if [[ ${pstate:-} == "P0" && ${util:-100} -le 5 ]]; then
add_finding "NVIDIA GPU is in P0 high-performance state while mostly idle; this can increase heat and trigger thermal limits."
add_action "If laptop has hybrid graphics, prefer iGPU mode for desktop workloads and use dGPU on demand."
fi
else
run_and_log "PCI VGA Devices" lspci -nnk | grep -A3 -Ei 'vga|3d|display'
fi
}
check_journal_size() {
local journal_line
journal_line=$(journalctl --disk-usage 2> /dev/null || true)
echo "Journal usage: ${journal_line:-unknown}" >> "$REPORT_FILE"
local journal_line
journal_line=$(journalctl --disk-usage 2>/dev/null || true)
echo "Journal usage: ${journal_line:-unknown}" >>"$REPORT_FILE"
if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ (G|M) ]]; then
local value unit
value="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
if [[ $unit == "G" ]]; then
add_finding "Systemd journal is large (${value}G); excessive logs can waste I/O and disk space."
fi
fi
if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ (G|M) ]]; then
local value unit
value="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
if [[ $unit == "G" ]]; then
add_finding "Systemd journal is large (${value}G); excessive logs can waste I/O and disk space."
fi
fi
}
apply_safe_fixes() {
if [[ $APPLY_SAFE_FIXES != "true" ]]; then
return
fi
if [[ $APPLY_SAFE_FIXES != "true" ]]; then
return
fi
log_info "Applying safe fixes..."
log_info "Applying safe fixes..."
if ! systemctl is-enabled fstrim.timer > /dev/null 2>&1; then
systemctl enable --now fstrim.timer
add_action "Enabled and started fstrim.timer."
fi
if ! systemctl is-enabled fstrim.timer >/dev/null 2>&1; then
systemctl enable --now fstrim.timer
add_action "Enabled and started fstrim.timer."
fi
if systemctl is-enabled tlp.service > /dev/null 2>&1 && systemctl is-enabled power-profiles-daemon.service > /dev/null 2>&1; then
systemctl disable --now tlp.service
add_action "Disabled tlp.service to avoid conflict with power-profiles-daemon."
fi
if systemctl is-enabled tlp.service >/dev/null 2>&1 && systemctl is-enabled power-profiles-daemon.service >/dev/null 2>&1; then
systemctl disable --now tlp.service
add_action "Disabled tlp.service to avoid conflict with power-profiles-daemon."
fi
local journal_line
journal_line=$(journalctl --disk-usage 2> /dev/null || true)
if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ G ]]; then
journalctl --vacuum-size=300M
add_action "Vacuumed systemd journal to 300M."
fi
local journal_line
journal_line=$(journalctl --disk-usage 2>/dev/null || true)
if [[ $journal_line =~ ([0-9]+\.?[0-9]*)\ G ]]; then
journalctl --vacuum-size=300M
add_action "Vacuumed systemd journal to 300M."
fi
}
print_summary() {
echo
echo "=============================="
echo " Arch Performance Diagnostics"
echo "=============================="
echo "Report: $REPORT_FILE"
echo
echo
echo "=============================="
echo " Arch Performance Diagnostics"
echo "=============================="
echo "Report: $REPORT_FILE"
echo
if [[ ${#FINDINGS[@]} -eq 0 ]]; then
log_ok "No high-confidence bottlenecks detected by automated checks."
else
log_warn "Likely issues found (${#FINDINGS[@]}):"
local item
for item in "${FINDINGS[@]}"; do
echo " - $item"
done
fi
if [[ ${#FINDINGS[@]} -eq 0 ]]; then
log_ok "No high-confidence bottlenecks detected by automated checks."
else
log_warn "Likely issues found (${#FINDINGS[@]}):"
local item
for item in "${FINDINGS[@]}"; do
echo " - $item"
done
fi
if [[ ${#ACTIONS[@]} -gt 0 ]]; then
echo
log_info "Actions/recommendations:"
local action
for action in "${ACTIONS[@]}"; do
echo " - $action"
done
fi
if [[ ${#ACTIONS[@]} -gt 0 ]]; then
echo
log_info "Actions/recommendations:"
local action
for action in "${ACTIONS[@]}"; do
echo " - $action"
done
fi
echo
echo "Recommended next command for deep per-process analysis:"
echo " sudo iotop -oPa"
echo " top"
echo " systemd-analyze blame"
echo
echo "Recommended next command for deep per-process analysis:"
echo " sudo iotop -oPa"
echo " top"
echo " systemd-analyze blame"
}
main() {
parse_args "$@"
check_root_if_needed "$@"
parse_args "$@"
check_root_if_needed "$@"
mkdir -p "$REPORT_DIR"
log_info "Writing diagnostic report to: $REPORT_FILE"
mkdir -p "$REPORT_DIR"
log_info "Writing diagnostic report to: $REPORT_FILE"
collect_basics
install_optional_tools
check_cpu_governor
check_thermal_state
check_power_services
check_storage_health
check_memory_pressure
check_gpu_state
check_journal_size
apply_safe_fixes
print_summary
collect_basics
install_optional_tools
check_cpu_governor
check_thermal_state
check_power_services
check_storage_health
check_memory_pressure
check_gpu_state
check_journal_size
apply_safe_fixes
print_summary
}
main "$@"

View File

@ -30,12 +30,12 @@ shift "$COMMON_ARGS_SHIFT"
DRY_RUN=false
for arg in "$@"; do
case "$arg" in
--dry-run)
DRY_RUN=true
;;
-h | --help)
cat << 'EOF'
case "$arg" in
--dry-run)
DRY_RUN=true
;;
-h | --help)
cat <<'EOF'
fix_arch_performance.sh - Fix common Arch Linux laptop performance issues
Usage: fix_arch_performance.sh [OPTIONS]
@ -55,9 +55,9 @@ Fixes applied:
All fixes are idempotent and safe to re-run.
Xorg fixes require reboot/re-login to take effect.
EOF
exit 0
;;
esac
exit 0
;;
esac
done
require_root "$@"
@ -71,53 +71,53 @@ FIXES_SKIPPED=0
# Helper: run or print a fix depending on --dry-run / --interactive
# ---------------------------------------------------------------------------
apply_fix() {
local description="$1"
shift
local description="$1"
shift
echo ""
log_info "$description"
echo ""
log_info "$description"
if [[ $DRY_RUN == "true" ]]; then
echo " [dry-run] Would run: $*"
return 0
fi
if [[ $DRY_RUN == "true" ]]; then
echo " [dry-run] Would run: $*"
return 0
fi
if [[ $INTERACTIVE_MODE == "true" ]]; then
if ! ask_yes_no " Apply this fix?"; then
log_warn "Skipped."
((FIXES_SKIPPED++)) || true
return 0
fi
fi
if [[ $INTERACTIVE_MODE == "true" ]]; then
if ! ask_yes_no " Apply this fix?"; then
log_warn "Skipped."
((FIXES_SKIPPED++)) || true
return 0
fi
fi
if "$@"; then
log_ok "Done."
((FIXES_APPLIED++)) || true
else
log_error "Failed (non-fatal, continuing)."
fi
if "$@"; then
log_ok "Done."
((FIXES_APPLIED++)) || true
else
log_error "Failed (non-fatal, continuing)."
fi
}
# ===================================================================
# Fix 1: NVIDIA RenderAccel
# ===================================================================
fix_nvidia_render_accel() {
local conf="/etc/X11/xorg.conf.d/20-nvidia.conf"
local conf="/etc/X11/xorg.conf.d/20-nvidia.conf"
# Check if RenderAccel is already correct
if [[ -f $conf ]] && grep -qi 'RenderAccel.*true' "$conf"; then
log_ok "NVIDIA RenderAccel is already enabled — skipping."
return 0
fi
# Check if RenderAccel is already correct
if [[ -f $conf ]] && grep -qi 'RenderAccel.*true' "$conf"; then
log_ok "NVIDIA RenderAccel is already enabled — skipping."
return 0
fi
mkdir -p /etc/X11/xorg.conf.d
mkdir -p /etc/X11/xorg.conf.d
# Back up the current config if it exists and has the bad setting
if [[ -f $conf ]]; then
cp "$conf" "${conf}.bak.$(date +%Y%m%d_%H%M%S)"
fi
# Back up the current config if it exists and has the bad setting
if [[ -f $conf ]]; then
cp "$conf" "${conf}.bak.$(date +%Y%m%d_%H%M%S)"
fi
cat > "$conf" << 'XORGEOF'
cat >"$conf" <<'XORGEOF'
# NVIDIA configuration - hardware acceleration enabled
# Disabling RenderAccel forces Xorg into software rendering,
# causing 30%+ CPU usage on desktop. Keep this set to "true".
@ -128,131 +128,131 @@ Section "Device"
EndSection
XORGEOF
# Clean up old backups left by nvidia_troubleshoot.sh
rm -f /etc/X11/xorg.conf.d/20-nvidia.conf.backup.* 2> /dev/null || true
return 0
# Clean up old backups left by nvidia_troubleshoot.sh
rm -f /etc/X11/xorg.conf.d/20-nvidia.conf.backup.* 2>/dev/null || true
return 0
}
# ===================================================================
# Fix 2: Power management daemon + performance profile
# ===================================================================
fix_power_management() {
# Install power-profiles-daemon if missing
if ! pacman -Qi power-profiles-daemon > /dev/null 2>&1; then
log_info "Installing power-profiles-daemon..."
pacman -S --needed --noconfirm power-profiles-daemon
fi
# Install power-profiles-daemon if missing
if ! pacman -Qi power-profiles-daemon >/dev/null 2>&1; then
log_info "Installing power-profiles-daemon..."
pacman -S --needed --noconfirm power-profiles-daemon
fi
# Enable and start the service
if ! systemctl is-enabled power-profiles-daemon.service > /dev/null 2>&1; then
systemctl enable --now power-profiles-daemon.service
elif ! systemctl is-active power-profiles-daemon.service > /dev/null 2>&1; then
systemctl start power-profiles-daemon.service
fi
# Enable and start the service
if ! systemctl is-enabled power-profiles-daemon.service >/dev/null 2>&1; then
systemctl enable --now power-profiles-daemon.service
elif ! systemctl is-active power-profiles-daemon.service >/dev/null 2>&1; then
systemctl start power-profiles-daemon.service
fi
# Resolve TLP conflict if both are enabled
if systemctl is-enabled tlp.service > /dev/null 2>&1; then
log_warn "TLP conflicts with power-profiles-daemon — disabling TLP."
systemctl disable --now tlp.service
fi
# Resolve TLP conflict if both are enabled
if systemctl is-enabled tlp.service >/dev/null 2>&1; then
log_warn "TLP conflicts with power-profiles-daemon — disabling TLP."
systemctl disable --now tlp.service
fi
# Set performance profile (appropriate when plugged in with strong hardware)
sleep 1
if has_cmd powerprofilesctl; then
powerprofilesctl set performance
log_info "Power profile set to: $(powerprofilesctl get)"
fi
# Set performance profile (appropriate when plugged in with strong hardware)
sleep 1
if has_cmd powerprofilesctl; then
powerprofilesctl set performance
log_info "Power profile set to: $(powerprofilesctl get)"
fi
return 0
return 0
}
# ===================================================================
# Fix 3: Journal vacuum + permanent size cap
# ===================================================================
fix_journal() {
local usage_line
usage_line=$(journalctl --disk-usage 2> /dev/null || true)
local usage_line
usage_line=$(journalctl --disk-usage 2>/dev/null || true)
local needs_vacuum=false
if [[ $usage_line =~ ([0-9]+\.?[0-9]*)\ G ]]; then
needs_vacuum=true
fi
local needs_vacuum=false
if [[ $usage_line =~ ([0-9]+\.?[0-9]*)\ G ]]; then
needs_vacuum=true
fi
if [[ $needs_vacuum == "true" ]]; then
journalctl --vacuum-size=300M
else
log_ok "Journal is already under 1GiB."
fi
if [[ $needs_vacuum == "true" ]]; then
journalctl --vacuum-size=300M
else
log_ok "Journal is already under 1GiB."
fi
# Create permanent size cap via drop-in
local dropin_dir="/etc/systemd/journald.conf.d"
local dropin_file="$dropin_dir/size-limit.conf"
# Create permanent size cap via drop-in
local dropin_dir="/etc/systemd/journald.conf.d"
local dropin_file="$dropin_dir/size-limit.conf"
if [[ -f $dropin_file ]] && grep -q 'SystemMaxUse=300M' "$dropin_file"; then
log_ok "Journal size cap already configured."
else
mkdir -p "$dropin_dir"
cat > "$dropin_file" << 'JOURNALEOF'
if [[ -f $dropin_file ]] && grep -q 'SystemMaxUse=300M' "$dropin_file"; then
log_ok "Journal size cap already configured."
else
mkdir -p "$dropin_dir"
cat >"$dropin_file" <<'JOURNALEOF'
[Journal]
SystemMaxUse=300M
JOURNALEOF
systemctl restart systemd-journald
fi
systemctl restart systemd-journald
fi
return 0
return 0
}
# ===================================================================
# Fix 4: Disable NetworkManager-wait-online
# ===================================================================
fix_nm_wait_online() {
if ! systemctl is-enabled NetworkManager-wait-online.service > /dev/null 2>&1; then
log_ok "NetworkManager-wait-online is already disabled — skipping."
return 0
fi
if ! systemctl is-enabled NetworkManager-wait-online.service >/dev/null 2>&1; then
log_ok "NetworkManager-wait-online is already disabled — skipping."
return 0
fi
systemctl disable NetworkManager-wait-online.service
return 0
systemctl disable NetworkManager-wait-online.service
return 0
}
# ===================================================================
# Fix 5: media-organizer.service
# ===================================================================
fix_media_organizer() {
local service_file="/etc/systemd/system/media-organizer.service"
local service_file="/etc/systemd/system/media-organizer.service"
# Find the organize_downloads.sh script
local script_path=""
local candidates=(
"/home/kuhy/testsAndMisc/linux_configuration/scripts/utils/organize_downloads.sh"
"/home/kuhy/linux-configuration/scripts/utils/organize_downloads.sh"
)
for candidate in "${candidates[@]}"; do
if [[ -f $candidate ]]; then
script_path="$candidate"
break
fi
done
# Find the organize_downloads.sh script
local script_path=""
local candidates=(
"/home/kuhy/testsAndMisc/linux_configuration/scripts/utils/organize_downloads.sh"
"/home/kuhy/linux-configuration/scripts/utils/organize_downloads.sh"
)
for candidate in "${candidates[@]}"; do
if [[ -f $candidate ]]; then
script_path="$candidate"
break
fi
done
if [[ -z $script_path ]]; then
log_warn "organize_downloads.sh not found — skipping media-organizer fix."
return 0
fi
if [[ -z $script_path ]]; then
log_warn "organize_downloads.sh not found — skipping media-organizer fix."
return 0
fi
local target_user="${SUDO_USER:-kuhy}"
local target_user="${SUDO_USER:-kuhy}"
# Check if already correct
if [[ -f $service_file ]]; then
if grep -q "User=$target_user" "$service_file" \
&& grep -q "ExecStart=$script_path" "$service_file"; then
log_ok "media-organizer.service is already correctly configured — skipping."
return 0
fi
fi
# Check if already correct
if [[ -f $service_file ]]; then
if grep -q "User=$target_user" "$service_file" &&
grep -q "ExecStart=$script_path" "$service_file"; then
log_ok "media-organizer.service is already correctly configured — skipping."
return 0
fi
fi
systemctl stop media-organizer.service 2> /dev/null || true
systemctl stop media-organizer.service 2>/dev/null || true
cat > "$service_file" << EOF
cat >"$service_file" <<EOF
[Unit]
Description=Media File Organizer
After=graphical-session.target
@ -271,56 +271,56 @@ RemainAfterExit=no
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl reset-failed media-organizer.service 2> /dev/null || true
systemctl enable media-organizer.service
return 0
systemctl daemon-reload
systemctl reset-failed media-organizer.service 2>/dev/null || true
systemctl enable media-organizer.service
return 0
}
# ===================================================================
# Apply all fixes
# ===================================================================
main() {
apply_fix \
"Fix 1/5: Enable NVIDIA hardware acceleration (RenderAccel → true)" \
fix_nvidia_render_accel
apply_fix \
"Fix 1/5: Enable NVIDIA hardware acceleration (RenderAccel → true)" \
fix_nvidia_render_accel
apply_fix \
"Fix 2/5: Install/enable power-profiles-daemon + set performance profile" \
fix_power_management
apply_fix \
"Fix 2/5: Install/enable power-profiles-daemon + set performance profile" \
fix_power_management
apply_fix \
"Fix 3/5: Vacuum journal logs + set permanent 300M size cap" \
fix_journal
apply_fix \
"Fix 3/5: Vacuum journal logs + set permanent 300M size cap" \
fix_journal
apply_fix \
"Fix 4/5: Disable NetworkManager-wait-online.service (~6s boot saving)" \
fix_nm_wait_online
apply_fix \
"Fix 4/5: Disable NetworkManager-wait-online.service (~6s boot saving)" \
fix_nm_wait_online
apply_fix \
"Fix 5/5: Fix media-organizer.service (correct path and user)" \
fix_media_organizer
apply_fix \
"Fix 5/5: Fix media-organizer.service (correct path and user)" \
fix_media_organizer
# ---------------------------------------------------------------
# Summary
# ---------------------------------------------------------------
echo ""
echo "=============================="
echo " Performance Fix Summary"
echo "=============================="
# ---------------------------------------------------------------
# Summary
# ---------------------------------------------------------------
echo ""
echo "=============================="
echo " Performance Fix Summary"
echo "=============================="
if [[ $DRY_RUN == "true" ]]; then
log_info "Dry-run mode — no changes were made."
else
log_ok "Fixes applied: $FIXES_APPLIED"
if [[ $FIXES_SKIPPED -gt 0 ]]; then
log_warn "Fixes skipped: $FIXES_SKIPPED"
fi
fi
if [[ $DRY_RUN == "true" ]]; then
log_info "Dry-run mode — no changes were made."
else
log_ok "Fixes applied: $FIXES_APPLIED"
if [[ $FIXES_SKIPPED -gt 0 ]]; then
log_warn "Fixes skipped: $FIXES_SKIPPED"
fi
fi
echo ""
log_info "Reboot or re-login for xorg changes (Fix 1) to take effect."
log_info "After reboot, verify with: diagnose_arch_performance.sh"
echo ""
log_info "Reboot or re-login for xorg changes (Fix 1) to take effect."
log_info "After reboot, verify with: diagnose_arch_performance.sh"
}
main

View File

@ -14,39 +14,39 @@ SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
DEFAULT_ORGANIZE_SCRIPT="/home/kuhy/testsAndMisc/linux_configuration/scripts/utils/organize_downloads.sh"
LEGACY_ORGANIZE_SCRIPT="/home/kuhy/linux-configuration/scripts/utils/organize_downloads.sh"
if [[ -f $DEFAULT_ORGANIZE_SCRIPT ]]; then
ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT"
ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT"
elif [[ -f $LEGACY_ORGANIZE_SCRIPT ]]; then
ORGANIZE_SCRIPT="$LEGACY_ORGANIZE_SCRIPT"
ORGANIZE_SCRIPT="$LEGACY_ORGANIZE_SCRIPT"
else
ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT"
ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT"
fi
TARGET_USER="${SUDO_USER:-kuhy}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
log "This script needs to be run as root."
log "Re-executing with sudo..."
exec sudo "$0" "$@"
log "This script needs to be run as root."
log "Re-executing with sudo..."
exec sudo "$0" "$@"
fi
log "Fixing media-organizer.service..."
# Verify the organize_downloads.sh script exists
if [[ ! -f $ORGANIZE_SCRIPT ]]; then
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
exit 1
log "ERROR: organize_downloads.sh not found at $ORGANIZE_SCRIPT"
exit 1
fi
# Stop the service if running (ignore errors)
systemctl stop "$SERVICE_NAME.service" 2> /dev/null || true
systemctl stop "$SERVICE_NAME.service" 2>/dev/null || true
# Recreate the service file with correct configuration
cat > "$SERVICE_FILE" << EOF
cat >"$SERVICE_FILE" <<EOF
[Unit]
Description=Media File Organizer
After=graphical-session.target
@ -72,7 +72,7 @@ systemctl daemon-reload
log "Reloaded systemd daemon"
# Reset the failed state
systemctl reset-failed "$SERVICE_NAME.service" 2> /dev/null || true
systemctl reset-failed "$SERVICE_NAME.service" 2>/dev/null || true
log "Reset failed state"
# Re-enable the service
@ -82,9 +82,9 @@ log "Service enabled"
# Optionally start the service to verify it works
log "Starting service to verify fix..."
if systemctl start "$SERVICE_NAME.service"; then
log "SUCCESS: media-organizer.service started successfully!"
log "SUCCESS: media-organizer.service started successfully!"
else
log "WARNING: Service still has issues. Check: journalctl -u $SERVICE_NAME"
log "WARNING: Service still has issues. Check: journalctl -u $SERVICE_NAME"
fi
# Show current status

View File

@ -21,7 +21,7 @@ print_setup_header "NVIDIA Comprehensive Troubleshooter & GSP Disabler"
# Check if nvidia module is loaded
if ! lsmod | grep -q nvidia; then
echo "Warning: NVIDIA module not currently loaded"
echo "Warning: NVIDIA module not currently loaded"
fi
# Create modprobe configuration directory if it doesn't exist
@ -34,7 +34,7 @@ echo "======================================"
mkdir -p "$MODPROBE_DIR"
# Create the configuration file
cat > "$CONFIG_FILE" << EOF
cat >"$CONFIG_FILE" <<EOF
# Disable NVIDIA GSP firmware to prevent Vulkan failures and crashes
# Created by nvidia_troubleshoot.sh on $(date)
options nvidia NVreg_EnableGpuFirmware=0
@ -44,35 +44,35 @@ echo "✓ Configuration written to: $CONFIG_FILE"
# Function to backup file if it exists
backup_file() {
local file="$1"
if [[ -f $file ]]; then
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
echo "✓ Backed up $file"
fi
local file="$1"
if [[ -f $file ]]; then
cp "$file" "$file.backup.$(date +%Y%m%d_%H%M%S)"
echo "✓ Backed up $file"
fi
}
# Function to add or update xorg.conf for RenderAccel
configure_xorg() {
echo ""
echo "2. Configuring Xorg Settings..."
echo "==============================="
echo ""
echo "2. Configuring Xorg Settings..."
echo "==============================="
XORG_CONF="/etc/X11/xorg.conf"
XORG_CONF_D="/etc/X11/xorg.conf.d"
NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf"
XORG_CONF="/etc/X11/xorg.conf"
XORG_CONF_D="/etc/X11/xorg.conf.d"
NVIDIA_CONF="$XORG_CONF_D/20-nvidia.conf"
# Create xorg.conf.d directory if it doesn't exist
mkdir -p "$XORG_CONF_D"
# Create xorg.conf.d directory if it doesn't exist
mkdir -p "$XORG_CONF_D"
# Backup existing xorg.conf if it exists
backup_file "$XORG_CONF"
backup_file "$NVIDIA_CONF"
# Backup existing xorg.conf if it exists
backup_file "$XORG_CONF"
backup_file "$NVIDIA_CONF"
# Create NVIDIA-specific configuration
# NOTE: RenderAccel must be "true" (or omitted, since it defaults to true).
# Setting it to "false" forces software rendering, causing Xorg to consume
# 30%+ CPU on the desktop and making the system feel extremely sluggish.
cat > "$NVIDIA_CONF" << EOF
# Create NVIDIA-specific configuration
# NOTE: RenderAccel must be "true" (or omitted, since it defaults to true).
# Setting it to "false" forces software rendering, causing Xorg to consume
# 30%+ CPU on the desktop and making the system feel extremely sluggish.
cat >"$NVIDIA_CONF" <<EOF
# NVIDIA configuration - hardware acceleration enabled
# Created by nvidia_troubleshoot.sh on $(date)
Section "Device"
@ -82,106 +82,106 @@ Section "Device"
EndSection
EOF
echo "✓ Created $NVIDIA_CONF with RenderAccel enabled"
echo "✓ Created $NVIDIA_CONF with RenderAccel enabled"
}
# Function to add GCC mismatch workaround
configure_gcc_workaround() {
echo ""
echo "3. Configuring GCC Mismatch Workaround..."
echo "=========================================="
echo ""
echo "3. Configuring GCC Mismatch Workaround..."
echo "=========================================="
local PROFILE_FILE="/etc/profile"
local timestamp
timestamp=$(date)
backup_file "$PROFILE_FILE"
local PROFILE_FILE="/etc/profile"
local timestamp
timestamp=$(date)
backup_file "$PROFILE_FILE"
# Check if IGNORE_CC_MISMATCH is already set
if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then
{
printf '\n'
printf '# NVIDIA GCC version mismatch workaround\n'
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
printf 'export IGNORE_CC_MISMATCH=1\n'
} >> "$PROFILE_FILE"
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
else
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
fi
# Check if IGNORE_CC_MISMATCH is already set
if ! grep -q "IGNORE_CC_MISMATCH" "$PROFILE_FILE"; then
{
printf '\n'
printf '# NVIDIA GCC version mismatch workaround\n'
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
printf 'export IGNORE_CC_MISMATCH=1\n'
} >>"$PROFILE_FILE"
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
else
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
fi
}
# Function to install pyroveil for mesh shader issues
install_pyroveil() {
echo ""
echo "4. Pyroveil Setup for Mesh Shader Issues..."
echo "==========================================="
echo ""
echo "4. Pyroveil Setup for Mesh Shader Issues..."
echo "==========================================="
local user_home="/home/$SUDO_USER"
local pyroveil_dir="$user_home/pyroveil"
local user_home="/home/$SUDO_USER"
local pyroveil_dir="$user_home/pyroveil"
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 ""
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 ""
local install_pyroveil=true
local install_pyroveil=true
if [[ $INTERACTIVE_MODE == "true" ]]; then
read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
install_pyroveil=false
fi
else
echo "Auto-installing Pyroveil (use --interactive to prompt)"
fi
if [[ $INTERACTIVE_MODE == "true" ]]; then
read -p "Would you like to install Pyroveil? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
install_pyroveil=false
fi
else
echo "Auto-installing Pyroveil (use --interactive to prompt)"
fi
if [[ $install_pyroveil == "true" ]]; then
# Check for required dependencies
local missing_deps=()
if [[ $install_pyroveil == "true" ]]; then
# Check for required dependencies
local missing_deps=()
for dep in git cmake ninja gcc; do
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
for dep in git cmake ninja gcc; do
if ! command -v "$dep" &>/dev/null; then
missing_deps+=("$dep")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo "Missing dependencies: ${missing_deps[*]}"
echo "Please install them first. On Arch Linux:"
echo "pacman -S base-devel git cmake ninja"
return 1
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo "Missing dependencies: ${missing_deps[*]}"
echo "Please install them first. On Arch Linux:"
echo "pacman -S base-devel git cmake ninja"
return 1
fi
# Clone and build pyroveil as the original user
echo "Installing Pyroveil to $pyroveil_dir..."
# Clone and build pyroveil as the original user
echo "Installing Pyroveil to $pyroveil_dir..."
if [[ -d $pyroveil_dir ]]; then
echo "Pyroveil directory already exists. Updating..."
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
else
sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
fi
if [[ -d $pyroveil_dir ]]; then
echo "Pyroveil directory already exists. Updating..."
sudo -u "$SUDO_USER" bash -c "cd '$pyroveil_dir' && git pull"
else
sudo -u "$SUDO_USER" git clone https://github.com/HansKristian-Work/pyroveil.git "$pyroveil_dir"
fi
sudo -u "$SUDO_USER" bash -c "
sudo -u "$SUDO_USER" bash -c "
cd '$pyroveil_dir'
git submodule update --init
cmake . -Bbuild -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$user_home/.local
ninja -C build install
"
echo "✓ Pyroveil installed successfully"
echo ""
echo "To use Pyroveil with games that have mesh shader issues:"
echo "1. For Final Fantasy VII Rebirth:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%"
echo ""
echo "2. For Steam games, add to launch options:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%"
echo ""
echo "Available configs in: $pyroveil_dir/hacks/"
echo "✓ Pyroveil installed successfully"
echo ""
echo "To use Pyroveil with games that have mesh shader issues:"
echo "1. For Final Fantasy VII Rebirth:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=$pyroveil_dir/hacks/ffvii-rebirth-nvidia/pyroveil.json %command%"
echo ""
echo "2. For Steam games, add to launch options:"
echo " PYROVEIL=1 PYROVEIL_CONFIG=/path/to/config/pyroveil.json %command%"
echo ""
echo "Available configs in: $pyroveil_dir/hacks/"
# Create a helper script
cat > "$user_home/run-with-pyroveil.sh" << EOF
# Create a helper script
cat >"$user_home/run-with-pyroveil.sh" <<EOF
#!/bin/bash
# Helper script to run games with Pyroveil
# Usage: ./run-with-pyroveil.sh <config-name> <command>
@ -207,88 +207,88 @@ echo "Config file: \$PYROVEIL_CONFIG"
exec "\$@"
EOF
chown "$SUDO_USER:$SUDO_USER" "$user_home/run-with-pyroveil.sh"
chmod +x "$user_home/run-with-pyroveil.sh"
echo "✓ Created helper script: $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"
echo "✓ Created helper script: $user_home/run-with-pyroveil.sh"
else
echo "Skipping Pyroveil installation"
echo "Note: You can manually install it later for mesh shader issues"
fi
else
echo "Skipping Pyroveil installation"
echo "Note: You can manually install it later for mesh shader issues"
fi
}
# Function to check for kernel parameter modifications
suggest_kernel_params() {
echo ""
echo "5. Kernel Parameter Recommendations..."
echo "====================================="
echo ""
echo "5. Kernel Parameter Recommendations..."
echo "====================================="
echo "NVIDIA Driver Issues and Recommended Kernel Parameters:"
echo ""
echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors"
echo " (especially with nvidia-96xx drivers):"
echo " → Add 'nopat' to kernel parameters"
echo ""
echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:"
echo " → Consider disabling micro-op cache in BIOS settings"
echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs"
echo " → Helps with severe graphical glitches in Xwayland applications"
echo " → Note: Disabling micro-op cache reduces CPU performance"
echo ""
echo "To add kernel parameters:"
echo "1. Edit /etc/default/grub"
echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT"
echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg"
echo "4. Reboot"
echo ""
echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:"
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"'
echo "NVIDIA Driver Issues and Recommended Kernel Parameters:"
echo ""
echo "A) For 'conflicting memory type' or 'failed to allocate primary buffer' errors"
echo " (especially with nvidia-96xx drivers):"
echo " → Add 'nopat' to kernel parameters"
echo ""
echo "B) For OpenGL visual glitches, hangs, and errors with modern CPUs:"
echo " → Consider disabling micro-op cache in BIOS settings"
echo " → This affects Intel Sandy Bridge (2011+) and AMD Zen (2017+) CPUs"
echo " → Helps with severe graphical glitches in Xwayland applications"
echo " → Note: Disabling micro-op cache reduces CPU performance"
echo ""
echo "To add kernel parameters:"
echo "1. Edit /etc/default/grub"
echo "2. Add parameters to GRUB_CMDLINE_LINUX_DEFAULT"
echo "3. Run: grub-mkconfig -o /boot/grub/grub.cfg"
echo "4. Reboot"
echo ""
echo "Example GRUB_CMDLINE_LINUX_DEFAULT line:"
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet nopat"'
# Check current CPU for micro-op cache relevance
echo ""
echo "CPU Information (for micro-op cache consideration):"
if command -v lscpu &> /dev/null; then
local cpu_info
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
echo "Current CPU: $cpu_info"
# Check current CPU for micro-op cache relevance
echo ""
echo "CPU Information (for micro-op cache consideration):"
if command -v lscpu &>/dev/null; then
local cpu_info
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
echo "Current CPU: $cpu_info"
if echo "$cpu_info" | grep -qi "intel"; then
echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache"
elif echo "$cpu_info" | grep -qi "amd"; then
echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache"
fi
fi
if echo "$cpu_info" | grep -qi "intel"; then
echo "→ Intel CPU detected. Sandy Bridge (2011) and later have micro-op cache"
elif echo "$cpu_info" | grep -qi "amd"; then
echo "→ AMD CPU detected. Zen (2017) and later have micro-op cache"
fi
fi
}
# Function to suggest desktop environment settings
suggest_desktop_settings() {
echo ""
echo "6. Desktop Environment Recommendations..."
echo "========================================"
echo ""
echo "6. Desktop Environment Recommendations..."
echo "========================================"
echo "For fullscreen application freezing/crashing issues:"
echo ""
echo "Enable Display Compositing and Direct fullscreen rendering:"
echo ""
echo "• KDE Plasma:"
echo " System Settings → Display and Monitor → Compositor"
echo " → Enable compositor + Enable direct rendering for fullscreen windows"
echo ""
echo "• GNOME:"
echo " Use Extensions or dconf-editor to enable compositing features"
echo ""
echo "• XFCE:"
echo " Settings → Window Manager Tweaks → Compositor"
echo " → Enable display compositing"
echo ""
echo "• Cinnamon:"
echo " System Settings → Effects → Enable desktop effects"
echo "For fullscreen application freezing/crashing issues:"
echo ""
echo "Enable Display Compositing and Direct fullscreen rendering:"
echo ""
echo "• KDE Plasma:"
echo " System Settings → Display and Monitor → Compositor"
echo " → Enable compositor + Enable direct rendering for fullscreen windows"
echo ""
echo "• GNOME:"
echo " Use Extensions or dconf-editor to enable compositing features"
echo ""
echo "• XFCE:"
echo " Settings → Window Manager Tweaks → Compositor"
echo " → Enable display compositing"
echo ""
echo "• Cinnamon:"
echo " System Settings → Effects → Enable desktop effects"
# Detect current desktop environment
if [[ -n $XDG_CURRENT_DESKTOP ]]; then
echo ""
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
fi
# Detect current desktop environment
if [[ -n $XDG_CURRENT_DESKTOP ]]; then
echo ""
echo "Detected desktop environment: $XDG_CURRENT_DESKTOP"
fi
}
# Apply all configurations
@ -300,14 +300,14 @@ install_pyroveil
echo ""
echo "7. Regenerating Initramfs..."
echo "============================"
if command -v mkinitcpio &> /dev/null; then
mkinitcpio -P
echo "✓ Initramfs regenerated with mkinitcpio"
elif command -v dracut &> /dev/null; then
dracut --force
echo "✓ Initramfs regenerated with dracut"
if command -v mkinitcpio &>/dev/null; then
mkinitcpio -P
echo "✓ Initramfs regenerated with mkinitcpio"
elif command -v dracut &>/dev/null; then
dracut --force
echo "✓ Initramfs regenerated with dracut"
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
# Display all recommendations
@ -323,7 +323,7 @@ echo "✓ GSP firmware disabled"
echo "✓ RenderAccel disabled in Xorg configuration"
echo "✓ GCC version mismatch workaround added"
if [[ -d "/home/$SUDO_USER/pyroveil" ]]; then
echo "✓ Pyroveil installed for mesh shader issues"
echo "✓ Pyroveil installed for mesh shader issues"
fi
echo "✓ Initramfs regenerated"
echo ""

View File

@ -19,8 +19,8 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
WITH_SERVER=false
@ -30,154 +30,160 @@ DUCKDNS_DOMAIN=""
DUCKDNS_TOKEN=""
for arg in "$@"; do
case "$arg" in
--with-server) WITH_SERVER=true ;;
--help|-h)
echo "Usage: $0 [--with-server]"
echo ""
echo "Options:"
echo " --with-server Also set up Joplin Server via Docker"
echo " --help, -h Show this help message"
exit 0
;;
*)
error "Unknown argument: $arg"
exit 1
;;
esac
case "$arg" in
--with-server) WITH_SERVER=true ;;
--help | -h)
echo "Usage: $0 [--with-server]"
echo ""
echo "Options:"
echo " --with-server Also set up Joplin Server via Docker"
echo " --help, -h Show this help message"
exit 0
;;
*)
error "Unknown argument: $arg"
exit 1
;;
esac
done
# ── Check prerequisites ─────────────────────────────────────────────
command -v pacman >/dev/null 2>&1 || { error "pacman not found. This script is for Arch Linux."; exit 1; }
command -v pacman >/dev/null 2>&1 || {
error "pacman not found. This script is for Arch Linux."
exit 1
}
# ── Install Joplin Desktop ──────────────────────────────────────────
install_joplin_desktop() {
if [[ -f "$HOME/.joplin/Joplin.AppImage" ]]; then
info "Joplin desktop is already installed at $HOME/.joplin/Joplin.AppImage"
return
fi
if [[ -f "$HOME/.joplin/Joplin.AppImage" ]]; then
info "Joplin desktop is already installed at $HOME/.joplin/Joplin.AppImage"
return
fi
info "Installing Joplin desktop app via official installer (AppImage)..."
info "Installing Joplin desktop app via official installer (AppImage)..."
# Official Joplin install script downloads the latest AppImage
wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh | bash
# Official Joplin install script downloads the latest AppImage
wget -O - https://raw.githubusercontent.com/laurent22/joplin/dev/Joplin_install_and_update.sh | bash
info "Joplin desktop installed at ~/.joplin/Joplin.AppImage"
info "Launch with: ~/.joplin/Joplin.AppImage (or 'joplin-desktop' from menu)"
info "Joplin desktop installed at ~/.joplin/Joplin.AppImage"
info "Launch with: ~/.joplin/Joplin.AppImage (or 'joplin-desktop' from menu)"
}
# ── Set up DuckDNS for stable URL ───────────────────────────────────
setup_duckdns() {
info "Setting up DuckDNS for stable server URL..."
info "Setting up DuckDNS for stable server URL..."
if [[ -z "$DUCKDNS_DOMAIN" ]]; then
echo ""
info "Your public IP may change. DuckDNS provides a free stable hostname."
info "1. Go to https://www.duckdns.org/ and sign in (Google/GitHub/etc.)"
info "2. Create a subdomain (e.g. 'myjoplin' for myjoplin.duckdns.org)"
info "3. Copy your token from the DuckDNS dashboard"
echo ""
read -r -p "Enter your DuckDNS subdomain (without .duckdns.org): " DUCKDNS_DOMAIN
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
fi
if [[ -z "$DUCKDNS_DOMAIN" ]]; then
echo ""
info "Your public IP may change. DuckDNS provides a free stable hostname."
info "1. Go to https://www.duckdns.org/ and sign in (Google/GitHub/etc.)"
info "2. Create a subdomain (e.g. 'myjoplin' for myjoplin.duckdns.org)"
info "3. Copy your token from the DuckDNS dashboard"
echo ""
read -r -p "Enter your DuckDNS subdomain (without .duckdns.org): " DUCKDNS_DOMAIN
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
fi
if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then
warn "DuckDNS not configured. Falling back to raw public IP (may change!)."
return 1
fi
if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then
warn "DuckDNS not configured. Falling back to raw public IP (may change!)."
return 1
fi
local full_domain="${DUCKDNS_DOMAIN}.duckdns.org"
local full_domain="${DUCKDNS_DOMAIN}.duckdns.org"
# Update DuckDNS now
info "Updating DuckDNS record for ${full_domain}..."
local result
result=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
if [[ "$result" == "OK" ]]; then
info "DuckDNS updated successfully: ${full_domain}"
else
warn "DuckDNS update returned: $result"
fi
# Update DuckDNS now
info "Updating DuckDNS record for ${full_domain}..."
local result
result=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
if [[ "$result" == "OK" ]]; then
info "DuckDNS updated successfully: ${full_domain}"
else
warn "DuckDNS update returned: $result"
fi
# Set up cron job to keep IP updated every 5 minutes
local duckdns_script="$JOPLIN_DATA_DIR/duckdns-update.sh"
cat > "$duckdns_script" <<DUCKEOF
# Set up cron job to keep IP updated every 5 minutes
local duckdns_script="$JOPLIN_DATA_DIR/duckdns-update.sh"
cat >"$duckdns_script" <<DUCKEOF
#!/usr/bin/env bash
result=\$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
echo "\$(date): \$result" >> "$JOPLIN_DATA_DIR/duckdns.log"
DUCKEOF
chmod +x "$duckdns_script"
chmod +x "$duckdns_script"
# Add cron job (remove old one if exists, add new)
(crontab -l 2>/dev/null | grep -v "duckdns-update.sh"; echo "*/5 * * * * $duckdns_script") | crontab -
info "DuckDNS cron job installed (updates every 5 minutes)"
# Add cron job (remove old one if exists, add new)
(
crontab -l 2>/dev/null | grep -v "duckdns-update.sh"
echo "*/5 * * * * $duckdns_script"
) | crontab -
info "DuckDNS cron job installed (updates every 5 minutes)"
# Save config for future runs
local config_file="$JOPLIN_DATA_DIR/.duckdns.conf"
cat > "$config_file" <<CONFEOF
# Save config for future runs
local config_file="$JOPLIN_DATA_DIR/.duckdns.conf"
cat >"$config_file" <<CONFEOF
DUCKDNS_DOMAIN="${DUCKDNS_DOMAIN}"
DUCKDNS_TOKEN="${DUCKDNS_TOKEN}"
CONFEOF
chmod 600 "$config_file"
chmod 600 "$config_file"
return 0
return 0
}
# ── Set up Joplin Server (Docker) ───────────────────────────────────
setup_joplin_server() {
info "Setting up Joplin Server via Docker..."
info "Setting up Joplin Server via Docker..."
# Ensure Docker is installed and running
if ! command -v docker >/dev/null 2>&1; then
info "Installing Docker..."
sudo pacman -S --needed --noconfirm docker docker-compose
fi
# Ensure Docker is installed and running
if ! command -v docker >/dev/null 2>&1; then
info "Installing Docker..."
sudo pacman -S --needed --noconfirm docker docker-compose
fi
if ! systemctl is-active --quiet docker; then
info "Starting Docker service..."
sudo systemctl enable --now docker
fi
if ! systemctl is-active --quiet docker; then
info "Starting Docker service..."
sudo systemctl enable --now docker
fi
# Add user to docker group if not already a member
if ! groups | grep -q '\bdocker\b'; then
warn "Adding $USER to docker group (re-login required for group to take effect)."
sudo usermod -aG docker "$USER"
fi
# Add user to docker group if not already a member
if ! groups | grep -q '\bdocker\b'; then
warn "Adding $USER to docker group (re-login required for group to take effect)."
sudo usermod -aG docker "$USER"
fi
# Create data directory
mkdir -p "$JOPLIN_DATA_DIR"
# Create data directory
mkdir -p "$JOPLIN_DATA_DIR"
local compose_file="$JOPLIN_DATA_DIR/docker-compose.yml"
local compose_file="$JOPLIN_DATA_DIR/docker-compose.yml"
# Load saved DuckDNS config if it exists
if [[ -f "$JOPLIN_DATA_DIR/.duckdns.conf" ]]; then
# shellcheck source=/dev/null
source "$JOPLIN_DATA_DIR/.duckdns.conf"
fi
# Load saved DuckDNS config if it exists
if [[ -f "$JOPLIN_DATA_DIR/.duckdns.conf" ]]; then
# shellcheck source=/dev/null
source "$JOPLIN_DATA_DIR/.duckdns.conf"
fi
# Set up DuckDNS for a stable hostname
local server_url
if setup_duckdns; then
server_url="http://${DUCKDNS_DOMAIN}.duckdns.org:${JOPLIN_SERVER_PORT}"
info "Using stable DuckDNS URL: $server_url"
else
# Fallback to public IP
local host_ip
host_ip="$(curl -s --max-time 5 ifconfig.me 2>/dev/null)"
if [[ -z "$host_ip" ]]; then
host_ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')"
fi
if [[ -z "$host_ip" ]]; then
host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi
if [[ -z "$host_ip" ]]; then
warn "Could not detect external IP. Falling back to 0.0.0.0"
host_ip="0.0.0.0"
fi
server_url="http://${host_ip}:${JOPLIN_SERVER_PORT}"
warn "Using raw IP URL (may change!): $server_url"
fi
# Set up DuckDNS for a stable hostname
local server_url
if setup_duckdns; then
server_url="http://${DUCKDNS_DOMAIN}.duckdns.org:${JOPLIN_SERVER_PORT}"
info "Using stable DuckDNS URL: $server_url"
else
# Fallback to public IP
local host_ip
host_ip="$(curl -s --max-time 5 ifconfig.me 2>/dev/null)"
if [[ -z "$host_ip" ]]; then
host_ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')"
fi
if [[ -z "$host_ip" ]]; then
host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
fi
if [[ -z "$host_ip" ]]; then
warn "Could not detect external IP. Falling back to 0.0.0.0"
host_ip="0.0.0.0"
fi
server_url="http://${host_ip}:${JOPLIN_SERVER_PORT}"
warn "Using raw IP URL (may change!): $server_url"
fi
cat > "$compose_file" <<EOF
cat >"$compose_file" <<EOF
version: "3"
services:
@ -221,73 +227,73 @@ networks:
joplin-net:
EOF
info "Starting Joplin Server..."
docker compose -f "$compose_file" up -d
info "Starting Joplin Server..."
docker compose -f "$compose_file" up -d
echo ""
info "Joplin Server is running at: ${server_url}"
echo ""
echo " Default admin credentials:"
echo " Email: admin@localhost"
echo " Password: admin"
echo ""
warn "IMPORTANT: Change the default admin password and the database"
warn "password (POSTGRES_PASSWORD in $compose_file) immediately!"
echo ""
info "Firewall: opening port ${JOPLIN_SERVER_PORT}/tcp for external access..."
if command -v ufw >/dev/null 2>&1; then
sudo ufw allow "${JOPLIN_SERVER_PORT}/tcp" || warn "Could not configure ufw"
elif command -v firewall-cmd >/dev/null 2>&1; then
if sudo firewall-cmd --permanent --add-port="${JOPLIN_SERVER_PORT}/tcp" \
&& sudo firewall-cmd --reload; then
:
else
warn "Could not configure firewalld"
fi
elif command -v iptables >/dev/null 2>&1; then
sudo iptables -A INPUT -p tcp --dport "${JOPLIN_SERVER_PORT}" -j ACCEPT || warn "Could not configure iptables"
else
warn "No firewall tool found. Ensure port ${JOPLIN_SERVER_PORT}/tcp is open manually."
fi
echo ""
echo " To connect Joplin desktop/Android to this server:"
echo " 1. Open Joplin → Tools → Options → Synchronisation"
echo " 2. Set target to 'Joplin Server'"
echo " 3. Enter URL: ${server_url}"
echo " 4. Enter your Joplin Server email and password"
echo ""
echo " Server management:"
echo " Start: docker compose -f $compose_file up -d"
echo " Stop: docker compose -f $compose_file down"
echo " Logs: docker compose -f $compose_file logs -f"
echo " Update: docker compose -f $compose_file pull && docker compose -f $compose_file up -d"
echo ""
info "Joplin Server is running at: ${server_url}"
echo ""
echo " Default admin credentials:"
echo " Email: admin@localhost"
echo " Password: admin"
echo ""
warn "IMPORTANT: Change the default admin password and the database"
warn "password (POSTGRES_PASSWORD in $compose_file) immediately!"
echo ""
info "Firewall: opening port ${JOPLIN_SERVER_PORT}/tcp for external access..."
if command -v ufw >/dev/null 2>&1; then
sudo ufw allow "${JOPLIN_SERVER_PORT}/tcp" || warn "Could not configure ufw"
elif command -v firewall-cmd >/dev/null 2>&1; then
if sudo firewall-cmd --permanent --add-port="${JOPLIN_SERVER_PORT}/tcp" &&
sudo firewall-cmd --reload; then
:
else
warn "Could not configure firewalld"
fi
elif command -v iptables >/dev/null 2>&1; then
sudo iptables -A INPUT -p tcp --dport "${JOPLIN_SERVER_PORT}" -j ACCEPT || warn "Could not configure iptables"
else
warn "No firewall tool found. Ensure port ${JOPLIN_SERVER_PORT}/tcp is open manually."
fi
echo ""
echo " To connect Joplin desktop/Android to this server:"
echo " 1. Open Joplin → Tools → Options → Synchronisation"
echo " 2. Set target to 'Joplin Server'"
echo " 3. Enter URL: ${server_url}"
echo " 4. Enter your Joplin Server email and password"
echo ""
echo " Server management:"
echo " Start: docker compose -f $compose_file up -d"
echo " Stop: docker compose -f $compose_file down"
echo " Logs: docker compose -f $compose_file logs -f"
echo " Update: docker compose -f $compose_file pull && docker compose -f $compose_file up -d"
}
# ── Main ─────────────────────────────────────────────────────────────
main() {
echo "╔══════════════════════════════════════════════╗"
echo "║ Joplin Installation Script ║"
echo "║ Free & Open Source Note-Taking App ║"
echo "║ https://joplinapp.org ║"
echo "╚══════════════════════════════════════════════╝"
echo ""
echo "╔══════════════════════════════════════════════╗"
echo "║ Joplin Installation Script ║"
echo "║ Free & Open Source Note-Taking App ║"
echo "║ https://joplinapp.org ║"
echo "╚══════════════════════════════════════════════╝"
echo ""
install_joplin_desktop
install_joplin_desktop
if [[ "$WITH_SERVER" == true ]]; then
setup_joplin_server
else
echo ""
info "Tip: Run with --with-server to also set up Joplin Server"
info "for self-hosted sync across devices (desktop + Android)."
fi
if [[ "$WITH_SERVER" == true ]]; then
setup_joplin_server
else
echo ""
info "Tip: Run with --with-server to also set up Joplin Server"
info "for self-hosted sync across devices (desktop + Android)."
fi
echo ""
info "Android app available at:"
info " Google Play: https://play.google.com/store/apps/details?id=net.cozic.joplin"
info " F-Droid: https://f-droid.org/packages/net.cozic.joplin/"
echo ""
info "Done!"
echo ""
info "Android app available at:"
info " Google Play: https://play.google.com/store/apps/details?id=net.cozic.joplin"
info " F-Droid: https://f-droid.org/packages/net.cozic.joplin/"
echo ""
info "Done!"
}
main

View File

@ -20,11 +20,11 @@ REMOVE_FILE="$MODULE_DIR/remove"
mkdir -p "$GUARDIAN_DIR"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
}
# Initialize control file if not exists
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" > "$CONTROL_FILE"
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" >"$CONTROL_FILE"
log "=== Android Guardian starting ==="
@ -36,73 +36,73 @@ log "Wireless ADB enabled on port 5555"
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
is_enabled() {
[ "$(cat "$CONTROL_FILE" 2> /dev/null)" = "ENABLED" ]
[ "$(cat "$CONTROL_FILE" 2>/dev/null)" = "ENABLED" ]
}
# Function to protect module from being disabled via Magisk UI
protect_module() {
# Remove disable file if someone tried to disable via Magisk
if [ -f "$DISABLE_FILE" ]; then
log "Module disable attempt detected via Magisk UI! Re-enabling..."
rm -f "$DISABLE_FILE"
log "Module re-enabled"
fi
# Remove disable file if someone tried to disable via Magisk
if [ -f "$DISABLE_FILE" ]; then
log "Module disable attempt detected via Magisk UI! Re-enabling..."
rm -f "$DISABLE_FILE"
log "Module re-enabled"
fi
# Remove remove file if someone tried to uninstall via Magisk
if [ -f "$REMOVE_FILE" ]; then
log "Module removal attempt detected via Magisk UI! Blocking..."
rm -f "$REMOVE_FILE"
log "Module removal blocked"
fi
# Remove remove file if someone tried to uninstall via Magisk
if [ -f "$REMOVE_FILE" ]; then
log "Module removal attempt detected via Magisk UI! Blocking..."
rm -f "$REMOVE_FILE"
log "Module removal blocked"
fi
}
# Function to restore hosts file if tampered
protect_hosts() {
if [ -f "$HOSTS_BACKUP" ]; then
current_hash=$(md5sum /system/etc/hosts 2> /dev/null | cut -d' ' -f1)
backup_hash=$(md5sum "$HOSTS_BACKUP" 2> /dev/null | cut -d' ' -f1)
if [ -f "$HOSTS_BACKUP" ]; then
current_hash=$(md5sum /system/etc/hosts 2>/dev/null | cut -d' ' -f1)
backup_hash=$(md5sum "$HOSTS_BACKUP" 2>/dev/null | cut -d' ' -f1)
if [ "$current_hash" != "$backup_hash" ]; then
log "Hosts file tampering detected! Restoring..."
cp "$HOSTS_BACKUP" "$MODDIR/system/etc/hosts"
log "Hosts file restored"
fi
fi
if [ "$current_hash" != "$backup_hash" ]; then
log "Hosts file tampering detected! Restoring..."
cp "$HOSTS_BACKUP" "$MODDIR/system/etc/hosts"
log "Hosts file restored"
fi
fi
}
# Function to uninstall blocked apps
check_blocked_apps() {
if [ ! -f "$BLOCKED_APPS_FILE" ]; then
return
fi
if [ ! -f "$BLOCKED_APPS_FILE" ]; then
return
fi
while IFS= read -r package || [ -n "$package" ]; do
# Skip comments and empty lines
case "$package" in
\#* | "") continue ;;
esac
while IFS= read -r package || [ -n "$package" ]; do
# Skip comments and empty lines
case "$package" in
\#* | "") continue ;;
esac
# Check if package is installed
if pm list packages 2> /dev/null | grep -q "package:$package"; then
log "Blocked app detected: $package - Uninstalling..."
pm uninstall "$package" 2> /dev/null && log "Uninstalled: $package" || log "Failed to uninstall: $package"
fi
done < "$BLOCKED_APPS_FILE"
# Check if package is installed
if pm list packages 2>/dev/null | grep -q "package:$package"; then
log "Blocked app detected: $package - Uninstalling..."
pm uninstall "$package" 2>/dev/null && log "Uninstalled: $package" || log "Failed to uninstall: $package"
fi
done <"$BLOCKED_APPS_FILE"
}
# Main monitoring loop - runs every 5 seconds for faster protection
while true; do
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
# This ensures only ADB can control the guardian
protect_module
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
# This ensures only ADB can control the guardian
protect_module
if is_enabled; then
protect_hosts
check_blocked_apps
fi
if is_enabled; then
protect_hosts
check_blocked_apps
fi
# Check every 5 seconds (faster response to disable attempts)
sleep 5
# Check every 5 seconds (faster response to disable attempts)
sleep 5
done &
log "Guardian service started (PID: $!)"

View File

@ -16,32 +16,32 @@ MODULE_DEST="/data/adb/modules/android_guardian"
# Ensure android-tools (adb) is installed
ensure_adb_installed() {
if command -v adb &> /dev/null; then
return 0
fi
if command -v adb &>/dev/null; then
return 0
fi
log "adb not found, installing android-tools..."
log "adb not found, installing android-tools..."
if command -v pacman &> /dev/null; then
sudo pacman -S --noconfirm android-tools || die "Failed to install android-tools"
elif command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y adb || die "Failed to install adb"
elif command -v dnf &> /dev/null; then
sudo dnf install -y android-tools || die "Failed to install android-tools"
else
die "adb not found and could not determine package manager. Please install android-tools manually."
fi
if command -v pacman &>/dev/null; then
sudo pacman -S --noconfirm android-tools || die "Failed to install android-tools"
elif command -v apt-get &>/dev/null; then
sudo apt-get update && sudo apt-get install -y adb || die "Failed to install adb"
elif command -v dnf &>/dev/null; then
sudo dnf install -y android-tools || die "Failed to install android-tools"
else
die "adb not found and could not determine package manager. Please install android-tools manually."
fi
# Verify installation
if ! command -v adb &> /dev/null; then
die "adb installation failed"
fi
# Verify installation
if ! command -v adb &>/dev/null; then
die "adb installation failed"
fi
log "android-tools installed successfully"
log "android-tools installed successfully"
}
show_usage() {
cat << EOF
cat <<EOF
Usage: $(basename "$0") [COMMAND]
Commands:
@ -81,239 +81,239 @@ WIRELESS_CONFIG="$HOME/.config/android_guardian_wireless"
# Discover Android devices on the network using mDNS
discover_android_device() {
local found_address=""
local found_address=""
# Ensure avahi-browse is available
if ! command -v avahi-browse &> /dev/null; then
if command -v pacman &> /dev/null; then
echo "Installing avahi for device discovery..." >&2
sudo pacman -S --noconfirm avahi nss-mdns &> /dev/null || true
sudo systemctl enable --now avahi-daemon &> /dev/null || true
elif command -v apt-get &> /dev/null; then
sudo apt-get install -y avahi-utils &> /dev/null || true
fi
fi
# Ensure avahi-browse is available
if ! command -v avahi-browse &>/dev/null; then
if command -v pacman &>/dev/null; then
echo "Installing avahi for device discovery..." >&2
sudo pacman -S --noconfirm avahi nss-mdns &>/dev/null || true
sudo systemctl enable --now avahi-daemon &>/dev/null || true
elif command -v apt-get &>/dev/null; then
sudo apt-get install -y avahi-utils &>/dev/null || true
fi
fi
if command -v avahi-browse &> /dev/null; then
echo "Scanning for Android devices (5 seconds)..." >&2
if command -v avahi-browse &>/dev/null; then
echo "Scanning for Android devices (5 seconds)..." >&2
# Android wireless debugging advertises as _adb-tls-connect._tcp
local discovery_result
discovery_result=$(timeout 5 avahi-browse -rpt _adb-tls-connect._tcp 2> /dev/null | grep "^=" | head -1)
# Android wireless debugging advertises as _adb-tls-connect._tcp
local discovery_result
discovery_result=$(timeout 5 avahi-browse -rpt _adb-tls-connect._tcp 2>/dev/null | grep "^=" | head -1)
if [[ -n $discovery_result ]]; then
# Parse: =;eth0;IPv4;adb-...;_adb-tls-connect._tcp;local;hostname.local;192.168.x.x;port;...
local ip port
ip=$(echo "$discovery_result" | cut -d';' -f8)
port=$(echo "$discovery_result" | cut -d';' -f9)
if [[ -n $discovery_result ]]; then
# Parse: =;eth0;IPv4;adb-...;_adb-tls-connect._tcp;local;hostname.local;192.168.x.x;port;...
local ip port
ip=$(echo "$discovery_result" | cut -d';' -f8)
port=$(echo "$discovery_result" | cut -d';' -f9)
if [[ -n $ip && -n $port ]]; then
found_address="$ip:$port"
echo "✓ Found device: $found_address" >&2
fi
fi
fi
if [[ -n $ip && -n $port ]]; then
found_address="$ip:$port"
echo "✓ Found device: $found_address" >&2
fi
fi
fi
# Fallback: try adb's mdns discovery
if [[ -z $found_address ]]; then
echo "Trying adb mdns discovery..." >&2
# Fallback: try adb's mdns discovery
if [[ -z $found_address ]]; then
echo "Trying adb mdns discovery..." >&2
# adb can discover devices via mdns
local mdns_result
mdns_result=$(timeout 5 adb mdns services 2> /dev/null | grep -E "adb-tls-connect|_adb\._tcp" | head -1)
# adb can discover devices via mdns
local mdns_result
mdns_result=$(timeout 5 adb mdns services 2>/dev/null | grep -E "adb-tls-connect|_adb\._tcp" | head -1)
if [[ -n $mdns_result ]]; then
# Try to extract IP:port from the result
local service_name
service_name=$(echo "$mdns_result" | awk '{print $1}')
if [[ -n $service_name ]]; then
# Try connecting via service name
echo "Found service: $service_name" >&2
fi
fi
fi
if [[ -n $mdns_result ]]; then
# Try to extract IP:port from the result
local service_name
service_name=$(echo "$mdns_result" | awk '{print $1}')
if [[ -n $service_name ]]; then
# Try connecting via service name
echo "Found service: $service_name" >&2
fi
fi
fi
# Return found address (or empty)
echo "$found_address"
# Return found address (or empty)
echo "$found_address"
}
# Pair with device over WiFi (Android 11+)
cmd_pair() {
ensure_adb_installed
ensure_adb_installed
echo ""
echo "=== Wireless ADB Pairing (Android 11+) ==="
echo ""
echo "On your phone:"
echo " 1. Go to Settings > Developer Options > Wireless debugging"
echo " 2. Enable Wireless debugging"
echo " 3. Tap 'Pair device with pairing code'"
echo " 4. Note the IP:port and pairing code shown"
echo ""
echo ""
echo "=== Wireless ADB Pairing (Android 11+) ==="
echo ""
echo "On your phone:"
echo " 1. Go to Settings > Developer Options > Wireless debugging"
echo " 2. Enable Wireless debugging"
echo " 3. Tap 'Pair device with pairing code'"
echo " 4. Note the IP:port and pairing code shown"
echo ""
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 IP:port (e.g., 192.168.1.100:37123): " pair_address
read -rp "Enter pairing code: " pair_code
if [[ -z $pair_address || -z $pair_code ]]; then
die "Pairing address and code are required"
fi
if [[ -z $pair_address || -z $pair_code ]]; then
die "Pairing address and code are required"
fi
log "Pairing with device at $pair_address..."
if adb pair "$pair_address" "$pair_code"; then
echo ""
echo "✓ Pairing successful!"
echo ""
echo "Now get the connection address:"
echo " On phone: Wireless debugging screen shows IP:port under 'IP address & Port'"
echo " (This is DIFFERENT from the pairing port)"
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
log "Pairing with device at $pair_address..."
if adb pair "$pair_address" "$pair_code"; then
echo ""
echo "✓ Pairing successful!"
echo ""
echo "Now get the connection address:"
echo " On phone: Wireless debugging screen shows IP:port under 'IP address & Port'"
echo " (This is DIFFERENT from the pairing port)"
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
if [[ -n $connect_address ]]; then
# Save for future connections
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" > "$WIRELESS_CONFIG"
log "Saved connection address for future use"
if [[ -n $connect_address ]]; then
# Save for future connections
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" >"$WIRELESS_CONFIG"
log "Saved connection address for future use"
# Connect now
cmd_connect
fi
else
die "Pairing failed. Make sure the code is correct and you're on the same network."
fi
# Connect now
cmd_connect
fi
else
die "Pairing failed. Make sure the code is correct and you're on the same network."
fi
}
# Connect to already-paired device
cmd_connect() {
ensure_adb_installed
ensure_adb_installed
local connect_address=""
local connect_address=""
# Check for saved address
if [[ -f $WIRELESS_CONFIG ]]; then
connect_address=$(cat "$WIRELESS_CONFIG")
log "Using saved address: $connect_address"
fi
# Check for saved address
if [[ -f $WIRELESS_CONFIG ]]; then
connect_address=$(cat "$WIRELESS_CONFIG")
log "Using saved address: $connect_address"
fi
# Try auto-discovery if no saved address
if [[ -z $connect_address ]]; then
echo ""
log "Searching for Android devices on network..."
connect_address=$(discover_android_device)
fi
# Try auto-discovery if no saved address
if [[ -z $connect_address ]]; then
echo ""
log "Searching for Android devices on network..."
connect_address=$(discover_android_device)
fi
# Manual fallback
if [[ -z $connect_address ]]; then
echo ""
echo "Auto-discovery failed. Enter address manually."
echo "On phone: Settings > Developer Options > Wireless debugging"
echo "Look for IP address & Port (NOT the pairing port)"
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
# Manual fallback
if [[ -z $connect_address ]]; then
echo ""
echo "Auto-discovery failed. Enter address manually."
echo "On phone: Settings > Developer Options > Wireless debugging"
echo "Look for IP address & Port (NOT the pairing port)"
echo ""
read -rp "Enter connection IP:port (e.g., 192.168.1.100:41567): " connect_address
if [[ -z $connect_address ]]; then
die "Connection address is required"
fi
fi
if [[ -z $connect_address ]]; then
die "Connection address is required"
fi
fi
# Save for future
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" > "$WIRELESS_CONFIG"
# Save for future
mkdir -p "$(dirname "$WIRELESS_CONFIG")"
echo "$connect_address" >"$WIRELESS_CONFIG"
log "Connecting to $connect_address..."
if adb connect "$connect_address" | grep -q "connected"; then
echo ""
echo "✓ Connected to device wirelessly!"
echo ""
log "Connecting to $connect_address..."
if adb connect "$connect_address" | grep -q "connected"; then
echo ""
echo "✓ Connected to device wirelessly!"
echo ""
# Verify connection
if adb devices | grep -q "$connect_address"; then
echo "Device ready. You can now run other commands."
fi
else
echo ""
echo "Connection failed. Possible issues:"
echo " - Wireless debugging not enabled on phone"
echo " - Phone and PC not on same WiFi network"
echo " - Port changed (check Wireless debugging screen)"
echo " - May need to pair first: $0 pair"
echo ""
# Clear saved config since it failed
rm -f "$WIRELESS_CONFIG"
exit 1
fi
# Verify connection
if adb devices | grep -q "$connect_address"; then
echo "Device ready. You can now run other commands."
fi
else
echo ""
echo "Connection failed. Possible issues:"
echo " - Wireless debugging not enabled on phone"
echo " - Phone and PC not on same WiFi network"
echo " - Port changed (check Wireless debugging screen)"
echo " - May need to pair first: $0 pair"
echo ""
# Clear saved config since it failed
rm -f "$WIRELESS_CONFIG"
exit 1
fi
}
# Disconnect wireless ADB
cmd_disconnect() {
ensure_adb_installed
ensure_adb_installed
log "Disconnecting all wireless devices..."
adb disconnect
echo "✓ Disconnected"
log "Disconnecting all wireless devices..."
adb disconnect
echo "✓ Disconnected"
}
# Check device connection and root
ensure_device_ready() {
ensure_adb_installed
ensure_adb_installed
# Check if any device is connected
if ! adb devices | grep -qE "device$|:.*device$"; then
echo ""
echo "No device connected!"
echo ""
echo "Options:"
echo " 1. Connect USB cable with debugging enabled"
echo " 2. Use wireless: $0 pair (first time) or $0 connect"
echo ""
# Check if any device is connected
if ! adb devices | grep -qE "device$|:.*device$"; then
echo ""
echo "No device connected!"
echo ""
echo "Options:"
echo " 1. Connect USB cable with debugging enabled"
echo " 2. Use wireless: $0 pair (first time) or $0 connect"
echo ""
# Check if we have a saved wireless config
if [[ -f $WIRELESS_CONFIG ]]; then
read -rp "Try connecting to saved wireless device? [Y/n]: " try_wireless
if [[ ${try_wireless,,} != "n" ]]; then
cmd_connect
else
exit 1
fi
else
exit 1
fi
fi
# Check if we have a saved wireless config
if [[ -f $WIRELESS_CONFIG ]]; then
read -rp "Try connecting to saved wireless device? [Y/n]: " try_wireless
if [[ ${try_wireless,,} != "n" ]]; then
cmd_connect
else
exit 1
fi
else
exit 1
fi
fi
check_adb_device
check_adb_root
check_adb_device
check_adb_root
}
# Build the module zip
build_module() {
local tmp_dir="$WORK_DIR/guardian_module"
local module_zip="$WORK_DIR/android_guardian.zip"
local tmp_dir="$WORK_DIR/guardian_module"
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"
mkdir -p "$tmp_dir/system/etc"
rm -rf "$tmp_dir"
mkdir -p "$tmp_dir/system/etc"
# Copy module files
cp "$GUARDIAN_MODULE_DIR/module.prop" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/service.sh" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/post-fs-data.sh" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/uninstall.sh" "$tmp_dir/"
# Copy module files
cp "$GUARDIAN_MODULE_DIR/module.prop" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/service.sh" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/post-fs-data.sh" "$tmp_dir/"
cp "$GUARDIAN_MODULE_DIR/uninstall.sh" "$tmp_dir/"
# Build hosts file
local hosts_file="$tmp_dir/system/etc/hosts"
if [[ -f /etc/hosts.stevenblack ]]; then
echo "[BUILD] Using StevenBlack hosts cache..." >&2
cp /etc/hosts.stevenblack "$hosts_file"
elif [[ -f /etc/hosts ]]; then
echo "[BUILD] Using /etc/hosts..." >&2
cp /etc/hosts "$hosts_file"
else
die "No hosts file found"
fi
# Build hosts file
local hosts_file="$tmp_dir/system/etc/hosts"
if [[ -f /etc/hosts.stevenblack ]]; then
echo "[BUILD] Using StevenBlack hosts cache..." >&2
cp /etc/hosts.stevenblack "$hosts_file"
elif [[ -f /etc/hosts ]]; then
echo "[BUILD] Using /etc/hosts..." >&2
cp /etc/hosts "$hosts_file"
else
die "No hosts file found"
fi
# Append custom blocking entries
cat >> "$hosts_file" << 'CUSTOM_EOF'
# Append custom blocking entries
cat >>"$hosts_file" <<'CUSTOM_EOF'
# ============================================
# Custom blocking entries - Android Guardian
@ -563,252 +563,252 @@ build_module() {
0.0.0.0 www.dominos.com
CUSTOM_EOF
local total_entries
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
echo "[BUILD] Hosts file contains $total_entries blocked domains" >&2
local total_entries
total_entries=$(grep -c "^0\.0\.0\.0 " "$hosts_file" || echo 0)
echo "[BUILD] Hosts file contains $total_entries blocked domains" >&2
# Create zip
(cd "$tmp_dir" && zip -r "$module_zip" . -x "*.DS_Store") > /dev/null
# Create zip
(cd "$tmp_dir" && zip -r "$module_zip" . -x "*.DS_Store") >/dev/null
echo "$module_zip"
echo "$module_zip"
}
# Install/update the guardian module
cmd_install() {
ensure_device_ready
ensure_device_ready
local module_zip
module_zip=$(build_module)
local module_zip
module_zip=$(build_module)
log "Pushing module to device..."
adb push "$module_zip" /sdcard/android_guardian.zip || die "Failed to push module"
log "Pushing module to device..."
adb push "$module_zip" /sdcard/android_guardian.zip || die "Failed to push module"
log "Installing module..."
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 'chmod 755 $MODULE_DEST/*.sh'"
adb shell "su -c 'rm /sdcard/android_guardian.zip'"
log "Installing module..."
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 'chmod 755 $MODULE_DEST/*.sh'"
adb shell "su -c 'rm /sdcard/android_guardian.zip'"
# Set up guardian data directory
log "Setting up guardian data..."
adb shell "su -c 'mkdir -p $GUARDIAN_DATA_DIR'"
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'"
# Set up guardian data directory
log "Setting up guardian data..."
adb shell "su -c 'mkdir -p $GUARDIAN_DATA_DIR'"
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'"
# Copy 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 'rm /sdcard/blocked_apps.txt'"
# Copy 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 'rm /sdcard/blocked_apps.txt'"
# Create hosts backup for tamper protection
adb shell "su -c 'cp $MODULE_DEST/system/etc/hosts $GUARDIAN_DATA_DIR/hosts.backup'"
# Create hosts backup for tamper protection
adb shell "su -c 'cp $MODULE_DEST/system/etc/hosts $GUARDIAN_DATA_DIR/hosts.backup'"
# Immediately uninstall any currently installed blocked apps
log "Checking for blocked apps to remove..."
uninstall_blocked_apps
# Immediately uninstall any currently installed blocked apps
log "Checking for blocked apps to remove..."
uninstall_blocked_apps
echo ""
echo "=========================================="
echo " ✓ Android Guardian installed!"
echo "=========================================="
echo ""
echo "Features enabled:"
echo " • Hosts-based ad/tracker blocking"
echo " • App installation blocking"
echo " • Tamper protection"
echo ""
echo "⚠️ This can ONLY be controlled via ADB:"
echo " Disable: $0 disable"
echo " Enable: $0 enable"
echo " Status: $0 status"
echo ""
echo "Reboot your device to activate the module."
echo ""
echo ""
echo "=========================================="
echo " ✓ Android Guardian installed!"
echo "=========================================="
echo ""
echo "Features enabled:"
echo " • Hosts-based ad/tracker blocking"
echo " • App installation blocking"
echo " • Tamper protection"
echo ""
echo "⚠️ This can ONLY be controlled via ADB:"
echo " Disable: $0 disable"
echo " Enable: $0 enable"
echo " Status: $0 status"
echo ""
echo "Reboot your device to activate the module."
echo ""
}
# Uninstall currently installed blocked apps
uninstall_blocked_apps() {
local blocked_apps
blocked_apps=$(grep -v '^#' "$GUARDIAN_MODULE_DIR/blocked_apps.txt" | grep -v '^$' || true)
local blocked_apps
blocked_apps=$(grep -v '^#' "$GUARDIAN_MODULE_DIR/blocked_apps.txt" | grep -v '^$' || true)
for package in $blocked_apps; do
if adb shell "pm list packages" 2> /dev/null | grep -q "package:$package"; then
log "Uninstalling blocked app: $package"
adb shell "pm uninstall $package" 2> /dev/null || true
fi
done
for package in $blocked_apps; do
if adb shell "pm list packages" 2>/dev/null | grep -q "package:$package"; then
log "Uninstalling blocked app: $package"
adb shell "pm uninstall $package" 2>/dev/null || true
fi
done
}
# Show status
cmd_status() {
ensure_device_ready
ensure_device_ready
echo ""
echo "=== Android Guardian Status ==="
echo ""
echo ""
echo "=== Android Guardian Status ==="
echo ""
# Check if module is installed
if adb shell "su -c 'test -d $MODULE_DEST'" 2> /dev/null; then
echo "Module: INSTALLED"
else
echo "Module: NOT INSTALLED"
return
fi
# Check if module is installed
if adb shell "su -c 'test -d $MODULE_DEST'" 2>/dev/null; then
echo "Module: INSTALLED"
else
echo "Module: NOT INSTALLED"
return
fi
# Check control status
local status
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo UNKNOWN'" | tr -d '\r')
echo "Status: $status"
# Check control status
local status
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo UNKNOWN'" | tr -d '\r')
echo "Status: $status"
# Check if module is "disabled" in Magisk UI (should be auto-fixed by watchdog)
local magisk_disabled
if adb shell "su -c 'test -f $MODULE_DEST/disable'" 2> /dev/null; then
magisk_disabled="YES (watchdog should fix this)"
else
magisk_disabled="No"
fi
echo "Magisk UI disabled: $magisk_disabled"
# Check if module is "disabled" in Magisk UI (should be auto-fixed by watchdog)
local magisk_disabled
if adb shell "su -c 'test -f $MODULE_DEST/disable'" 2>/dev/null; then
magisk_disabled="YES (watchdog should fix this)"
else
magisk_disabled="No"
fi
echo "Magisk UI disabled: $magisk_disabled"
# Check if watchdog is running
local watchdog_running
watchdog_running=$(adb shell "su -c 'pgrep -f watchdog.sh 2>/dev/null | wc -l'" | tr -d '\r')
if [ "$watchdog_running" -gt 0 ] 2> /dev/null; then
echo "Watchdog: RUNNING ($watchdog_running processes)"
else
echo "Watchdog: NOT RUNNING (reboot phone to start)"
fi
# Check if watchdog is running
local watchdog_running
watchdog_running=$(adb shell "su -c 'pgrep -f watchdog.sh 2>/dev/null | wc -l'" | tr -d '\r')
if [ "$watchdog_running" -gt 0 ] 2>/dev/null; then
echo "Watchdog: RUNNING ($watchdog_running processes)"
else
echo "Watchdog: NOT RUNNING (reboot phone to start)"
fi
# Check hosts file
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')
echo "Blocked domains: $hosts_entries"
# Check hosts file
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')
echo "Blocked domains: $hosts_entries"
# Check blocked apps 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')
echo "Blocked app rules: $blocked_count packages"
# Check blocked apps 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')
echo "Blocked app rules: $blocked_count packages"
echo ""
echo "Protection: Module cannot be disabled from Magisk UI"
echo " Only controllable via: $0 disable/enable"
echo ""
echo ""
echo "Protection: Module cannot be disabled from Magisk UI"
echo " Only controllable via: $0 disable/enable"
echo ""
}
# Disable guardian
cmd_disable() {
ensure_device_ready
ensure_device_ready
log "Disabling Android Guardian..."
adb shell "su -c 'echo DISABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to disable guardian"
log "Disabling Android Guardian..."
adb shell "su -c 'echo DISABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to disable guardian"
echo ""
echo "✓ Guardian DISABLED"
echo " Hosts blocking still active until reboot"
echo " App blocking service paused"
echo ""
echo "To re-enable: $0 enable"
echo ""
echo ""
echo "✓ Guardian DISABLED"
echo " Hosts blocking still active until reboot"
echo " App blocking service paused"
echo ""
echo "To re-enable: $0 enable"
echo ""
}
# Enable guardian
cmd_enable() {
ensure_device_ready
ensure_device_ready
log "Enabling Android Guardian..."
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to enable guardian"
log "Enabling Android Guardian..."
adb shell "su -c 'echo ENABLED > $GUARDIAN_DATA_DIR/control'" || die "Failed to enable guardian"
echo ""
echo "✓ Guardian ENABLED"
echo ""
echo ""
echo "✓ Guardian ENABLED"
echo ""
}
# Uninstall module
cmd_uninstall() {
ensure_device_ready
ensure_device_ready
# Check if disabled first
local status
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo ENABLED'" | tr -d '\r')
# Check if disabled first
local status
status=$(adb shell "su -c 'cat $GUARDIAN_DATA_DIR/control 2>/dev/null || echo ENABLED'" | tr -d '\r')
if [[ $status != "DISABLED" ]]; then
echo ""
echo "⚠️ Guardian must be disabled before uninstalling!"
echo " Run: $0 disable"
echo " Then: $0 uninstall"
echo ""
exit 1
fi
if [[ $status != "DISABLED" ]]; then
echo ""
echo "⚠️ Guardian must be disabled before uninstalling!"
echo " Run: $0 disable"
echo " Then: $0 uninstall"
echo ""
exit 1
fi
log "Removing Android Guardian..."
adb shell "su -c 'rm -rf $MODULE_DEST'"
adb shell "su -c 'rm -rf $GUARDIAN_DATA_DIR'"
log "Removing Android Guardian..."
adb shell "su -c 'rm -rf $MODULE_DEST'"
adb shell "su -c 'rm -rf $GUARDIAN_DATA_DIR'"
echo ""
echo "✓ Guardian uninstalled"
echo " Reboot to remove hosts blocking"
echo ""
echo ""
echo "✓ Guardian uninstalled"
echo " Reboot to remove hosts blocking"
echo ""
}
# Show logs
cmd_logs() {
ensure_device_ready
ensure_device_ready
echo "=== Guardian Logs ==="
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/guardian.log 2>/dev/null || echo \"No logs yet\"'"
echo "=== Guardian Logs ==="
adb shell "su -c 'cat $GUARDIAN_DATA_DIR/guardian.log 2>/dev/null || echo \"No logs yet\"'"
}
# Block an app
cmd_block_app() {
local package="${1:-}"
local package="${1:-}"
if [[ -z $package ]]; then
echo "Usage: $0 block-app <package.name>"
echo "Example: $0 block-app com.ubercab.eats"
exit 1
fi
if [[ -z $package ]]; then
echo "Usage: $0 block-app <package.name>"
echo "Example: $0 block-app com.ubercab.eats"
exit 1
fi
ensure_device_ready
ensure_device_ready
log "Adding $package to block list..."
adb shell "su -c 'echo \"$package\" >> $GUARDIAN_DATA_DIR/blocked_apps.txt'"
log "Adding $package to block list..."
adb shell "su -c 'echo \"$package\" >> $GUARDIAN_DATA_DIR/blocked_apps.txt'"
# Also add to local file
echo "$package" >> "$GUARDIAN_MODULE_DIR/blocked_apps.txt"
# Also add to local file
echo "$package" >>"$GUARDIAN_MODULE_DIR/blocked_apps.txt"
# Try to uninstall if currently installed
if adb shell "pm list packages" 2> /dev/null | grep -q "package:$package"; then
log "Uninstalling $package..."
adb shell "pm uninstall $package" 2> /dev/null || true
fi
# Try to uninstall if currently installed
if adb shell "pm list packages" 2>/dev/null | grep -q "package:$package"; then
log "Uninstalling $package..."
adb shell "pm uninstall $package" 2>/dev/null || true
fi
echo "$package added to block list"
echo "$package added to block list"
}
# Unblock an app
cmd_unblock_app() {
local package="${1:-}"
local package="${1:-}"
if [[ -z $package ]]; then
echo "Usage: $0 unblock-app <package.name>"
exit 1
fi
if [[ -z $package ]]; then
echo "Usage: $0 unblock-app <package.name>"
exit 1
fi
ensure_device_ready
ensure_device_ready
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'"
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'"
# 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"
# 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"
echo "$package removed from block list"
echo "$package removed from block list"
}
# List blocked apps
cmd_list_blocked() {
ensure_device_ready
ensure_device_ready
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"
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"
}
# Main
@ -819,48 +819,48 @@ COMMAND="${1:-install}"
shift || true
case "$COMMAND" in
install)
cmd_install
;;
status)
cmd_status
;;
disable)
cmd_disable
;;
enable)
cmd_enable
;;
uninstall)
cmd_uninstall
;;
logs)
cmd_logs
;;
block-app)
cmd_block_app "$@"
;;
unblock-app)
cmd_unblock_app "$@"
;;
list-blocked)
cmd_list_blocked
;;
pair)
cmd_pair
;;
connect)
cmd_connect
;;
disconnect)
cmd_disconnect
;;
-h | --help | help)
show_usage
;;
*)
echo "Unknown command: $COMMAND"
show_usage
exit 1
;;
install)
cmd_install
;;
status)
cmd_status
;;
disable)
cmd_disable
;;
enable)
cmd_enable
;;
uninstall)
cmd_uninstall
;;
logs)
cmd_logs
;;
block-app)
cmd_block_app "$@"
;;
unblock-app)
cmd_unblock_app "$@"
;;
list-blocked)
cmd_list_blocked
;;
pair)
cmd_pair
;;
connect)
cmd_connect
;;
disconnect)
cmd_disconnect
;;
-h | --help | help)
show_usage
;;
*)
echo "Unknown command: $COMMAND"
show_usage
exit 1
;;
esac

View File

@ -13,8 +13,8 @@ echo "WARNING: This may take a very long time (fetching ~2500 gminy)"
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -11,8 +11,8 @@ echo "=== Polish Powiaty Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Bridges Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -12,8 +12,8 @@ echo
# Create virtual environment if it doesn't exist
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
# Activate virtual environment

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Landmarks Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Metro Stations Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Osiedla Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Streets Anki Generator ==="
echo
if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi
echo "Activating virtual environment..."

14
setup.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Post-clone setup script for testsAndMisc repository.
# Run once after cloning: ./setup.sh
set -euo pipefail
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
printf 'Configuring git hooks path...\n'
git config core.hooksPath linux_configuration/.githooks
printf ' ✓ core.hooksPath set to linux_configuration/.githooks\n'
printf 'Setup complete.\n'