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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,8 +11,8 @@ echo "=== Warsaw Streets Anki Generator ==="
echo echo
if [ ! -d "$VENV_DIR" ]; then if [ ! -d "$VENV_DIR" ]; then
echo "Creating virtual environment..." echo "Creating virtual environment..."
python3 -m venv "$VENV_DIR" python3 -m venv "$VENV_DIR"
fi fi
echo "Activating virtual environment..." 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'