mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 15:23:11 +02:00
refactor: reduce code duplication from 1.97% to 0.76%
- Add common.sh library functions: require_imagemagick, install_missing_pacman_packages, handle_arg_help_or_unknown - Create android.sh shared library for Android utilities - Create hosts-guard-common.sh for pacman hooks shared functions - Update multiple scripts to source common.sh and use shared helpers - Add print_shutdown_schedule helper in setup_midnight_shutdown.sh - Remove duplicate log(), usage(), install_packages patterns across scripts - Format all shell scripts with shfmt (2-space indent)
This commit is contained in:
parent
3e336d4958
commit
5b032891c5
@ -61,7 +61,7 @@ printf 'Running duplicate code detection...\n'
|
|||||||
JSCPD_BIN="${HOME}/.local/node_modules/.bin/jscpd"
|
JSCPD_BIN="${HOME}/.local/node_modules/.bin/jscpd"
|
||||||
|
|
||||||
# Install jscpd if not present
|
# Install jscpd if not present
|
||||||
if [[ ! -x "$JSCPD_BIN" ]]; then
|
if [[ ! -x $JSCPD_BIN ]]; then
|
||||||
printf ' → jscpd not found, installing...\n'
|
printf ' → jscpd not found, installing...\n'
|
||||||
if ! npm install --prefix ~/.local jscpd 2>&1; then
|
if ! npm install --prefix ~/.local jscpd 2>&1; then
|
||||||
printf '\nCommit aborted: failed to install jscpd.\n' >&2
|
printf '\nCommit aborted: failed to install jscpd.\n' >&2
|
||||||
|
|||||||
@ -75,6 +75,17 @@ install_from_aur() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper: try to install from AUR and log result to done.txt/failed.txt
|
||||||
|
try_aur_install() {
|
||||||
|
local repo_url="$1"
|
||||||
|
local pkg_name="$2"
|
||||||
|
if install_from_aur "$repo_url" "$pkg_name"; then
|
||||||
|
echo "$pkg_name" >>done.txt
|
||||||
|
else
|
||||||
|
echo "$pkg_name" >>failed.txt
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
process_packages() {
|
process_packages() {
|
||||||
local file_path
|
local file_path
|
||||||
file_path="$1"
|
file_path="$1"
|
||||||
@ -105,18 +116,10 @@ process_packages() {
|
|||||||
echo "$pkg_name" >>failed.txt
|
echo "$pkg_name" >>failed.txt
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if install_from_aur "$repo_url" "$pkg_name"; then
|
try_aur_install "$repo_url" "$pkg_name"
|
||||||
echo "$pkg_name" >> done.txt
|
|
||||||
else
|
|
||||||
echo "$pkg_name" >> failed.txt
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if install_from_aur "$repo_url" "$pkg_name"; then
|
try_aur_install "$repo_url" "$pkg_name"
|
||||||
echo "$pkg_name" >> done.txt
|
|
||||||
else
|
|
||||||
echo "$pkg_name" >> failed.txt
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done <"$file_path"
|
done <"$file_path"
|
||||||
}
|
}
|
||||||
@ -174,6 +177,18 @@ while IFS= read -r line; do
|
|||||||
fi
|
fi
|
||||||
done <"aur_packages.txt"
|
done <"aur_packages.txt"
|
||||||
|
|
||||||
|
# Helper: Check if all subpackages are installed
|
||||||
|
# Returns 0 if ALL subpackages are installed, 1 otherwise
|
||||||
|
all_subpackages_installed() {
|
||||||
|
local -n sub_pkgs_ref=$1
|
||||||
|
for subpkg in "${sub_pkgs_ref[@]}"; do
|
||||||
|
if ! pacman -Qi "$subpkg" &>/dev/null; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# Read pacman packages from file
|
# Read pacman packages from file
|
||||||
declare -a pacman_packages
|
declare -a pacman_packages
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
@ -191,21 +206,15 @@ for pkg in "${pacman_packages[@]}"; do
|
|||||||
fi
|
fi
|
||||||
# Check for texlive subpackages
|
# Check for texlive subpackages
|
||||||
if [ "$pkg" == "texlive" ]; then
|
if [ "$pkg" == "texlive" ]; then
|
||||||
sub_pkgs=(
|
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
||||||
|
texlive_sub_pkgs=(
|
||||||
texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra
|
texlive-basic texlive-bibtexextra texlive-binextra texlive-context texlive-fontsextra
|
||||||
texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities
|
texlive-fontsrecommended texlive-fontutils texlive-formatsextra texlive-games texlive-humanities
|
||||||
texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience
|
texlive-latex texlive-latexextra texlive-latexrecommended texlive-luatex texlive-mathscience
|
||||||
texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks
|
texlive-metapost texlive-music texlive-pictures texlive-plaingeneric texlive-pstricks
|
||||||
texlive-publishers texlive-xetex
|
texlive-publishers texlive-xetex
|
||||||
)
|
)
|
||||||
all_installed=true
|
if all_subpackages_installed texlive_sub_pkgs; then
|
||||||
for subpkg in "${sub_pkgs[@]}"; do
|
|
||||||
if ! pacman -Qi "$subpkg" &> /dev/null; then
|
|
||||||
all_installed=false
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$all_installed" = true ]; then
|
|
||||||
echo "All texlive subpackages are installed, skipping texlive"
|
echo "All texlive subpackages are installed, skipping texlive"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@ -213,21 +222,15 @@ for pkg in "${pacman_packages[@]}"; do
|
|||||||
|
|
||||||
# Check for texlive-lang subpackages
|
# Check for texlive-lang subpackages
|
||||||
if [ "$pkg" == "texlive-lang" ]; then
|
if [ "$pkg" == "texlive-lang" ]; then
|
||||||
sub_pkgs=(
|
# shellcheck disable=SC2034 # Used via nameref in all_subpackages_installed
|
||||||
|
texlive_lang_sub_pkgs=(
|
||||||
texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic
|
texlive-langarabic texlive-langchinese texlive-langcjk texlive-langcyrillic
|
||||||
texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench
|
texlive-langczechslovak texlive-langenglish texlive-langeuropean texlive-langfrench
|
||||||
texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese
|
texlive-langgerman texlive-langgreek texlive-langitalian texlive-langjapanese
|
||||||
texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese
|
texlive-langkorean texlive-langother texlive-langpolish texlive-langportuguese
|
||||||
texlive-langspanish
|
texlive-langspanish
|
||||||
)
|
)
|
||||||
all_installed=true
|
if all_subpackages_installed texlive_lang_sub_pkgs; then
|
||||||
for subpkg in "${sub_pkgs[@]}"; do
|
|
||||||
if ! pacman -Qi "$subpkg" &> /dev/null; then
|
|
||||||
all_installed=false
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ "$all_installed" = true ]; then
|
|
||||||
echo "All texlive-lang subpackages are installed, skipping texlive-lang"
|
echo "All texlive-lang subpackages are installed, skipping texlive-lang"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|||||||
91
hosts/guard/pacman-hooks/hosts-guard-common.sh
Normal file
91
hosts/guard/pacman-hooks/hosts-guard-common.sh
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared functions for hosts-guard pacman hooks
|
||||||
|
# This file is sourced by pacman-pre-unlock-hosts.sh and pacman-post-relock-hosts.sh
|
||||||
|
|
||||||
|
TARGET=/etc/hosts
|
||||||
|
LOGTAG=hosts-guard-hook
|
||||||
|
|
||||||
|
# Check if target has a read-only mount
|
||||||
|
is_ro_mount() {
|
||||||
|
findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro
|
||||||
|
}
|
||||||
|
|
||||||
|
# Count mount layers for the target
|
||||||
|
mount_layers_count() {
|
||||||
|
awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collapse all bind mount layers
|
||||||
|
collapse_mounts() {
|
||||||
|
local i=0
|
||||||
|
if command -v mountpoint >/dev/null 2>&1; then
|
||||||
|
while mountpoint -q "$TARGET"; do
|
||||||
|
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||||
|
i=$((i + 1))
|
||||||
|
((i > 20)) && break
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local cnt
|
||||||
|
cnt=$(mount_layers_count)
|
||||||
|
while ((cnt > 1)); do
|
||||||
|
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||||
|
i=$((i + 1))
|
||||||
|
((i > 20)) && break
|
||||||
|
cnt=$(mount_layers_count)
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Stop systemd units related to hosts guard
|
||||||
|
stop_units_if_present() {
|
||||||
|
local units=(hosts-bind-mount.service hosts-guard.path)
|
||||||
|
for u in "${units[@]}"; do
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
|
||||||
|
systemctl stop "$u" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove immutable/append-only attributes
|
||||||
|
remove_host_attrs() {
|
||||||
|
if command -v lsattr >/dev/null 2>&1; then
|
||||||
|
local attrs
|
||||||
|
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
|
||||||
|
if echo "$attrs" | grep -q " i "; then
|
||||||
|
chattr -i "$TARGET" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
if echo "$attrs" | grep -q " a "; then
|
||||||
|
chattr -a "$TARGET" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply immutable attribute
|
||||||
|
apply_immutable() {
|
||||||
|
if command -v chattr >/dev/null 2>&1; then
|
||||||
|
chattr +i "$TARGET" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply a single read-only bind mount layer
|
||||||
|
apply_ro_bind_mount() {
|
||||||
|
mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true
|
||||||
|
mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start the path watcher service
|
||||||
|
start_path_watcher() {
|
||||||
|
if command -v systemctl >/dev/null 2>&1; then
|
||||||
|
systemctl start hosts-guard.path >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log to system logger and run log file
|
||||||
|
log_hook() {
|
||||||
|
local phase="$1"
|
||||||
|
local state="$2"
|
||||||
|
logger -t "$LOGTAG" "$phase: $state"
|
||||||
|
echo "$(date -Is) $phase-$state" >>/run/hosts-guard-hook.log 2>/dev/null || true
|
||||||
|
}
|
||||||
@ -3,54 +3,30 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TARGET=/etc/hosts
|
# Source shared functions
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=hosts-guard-common.sh
|
||||||
|
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||||
|
|
||||||
ENFORCE=/usr/local/sbin/enforce-hosts.sh
|
ENFORCE=/usr/local/sbin/enforce-hosts.sh
|
||||||
LOGTAG=hosts-guard-hook
|
|
||||||
|
|
||||||
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; }
|
log_hook "post" "relocking(start)"
|
||||||
collapse_mounts() {
|
|
||||||
local i=0
|
|
||||||
if command -v mountpoint >/dev/null 2>&1; then
|
|
||||||
while mountpoint -q "$TARGET"; do
|
|
||||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
|
||||||
i=$((i + 1))
|
|
||||||
((i > 20)) && break
|
|
||||||
done
|
|
||||||
else
|
|
||||||
local cnt
|
|
||||||
cnt=$(mount_layers_count)
|
|
||||||
while ((cnt > 1)); do
|
|
||||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
|
||||||
i=$((i + 1))
|
|
||||||
((i > 20)) && break
|
|
||||||
cnt=$(mount_layers_count)
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ensure we end with a single read-only bind mount layer
|
# Collapse any stacked mounts first
|
||||||
logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)"
|
|
||||||
echo "$(date -Is) post-relock(start)" >>/run/hosts-guard-hook.log 2>/dev/null || true
|
|
||||||
collapse_mounts
|
collapse_mounts
|
||||||
|
|
||||||
|
# Run enforcement script if available
|
||||||
if [[ -x $ENFORCE ]]; then
|
if [[ -x $ENFORCE ]]; then
|
||||||
"$ENFORCE" >/dev/null 2>&1 || true
|
"$ENFORCE" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v chattr >/dev/null 2>&1; then
|
# Apply protections
|
||||||
chattr +i "$TARGET" >/dev/null 2>&1 || true
|
apply_immutable
|
||||||
fi
|
apply_ro_bind_mount
|
||||||
|
|
||||||
# Apply exactly one ro bind layer
|
# Start the path watcher
|
||||||
mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true
|
start_path_watcher
|
||||||
mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# Start only the path watcher; avoid bind-mount service (we already bound once)
|
log_hook "post" "relocking(done)"
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
systemctl start hosts-guard.path >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
logger -t "$LOGTAG" "post: relocking /etc/hosts (done)"
|
|
||||||
echo "$(date -Is) post-relock(done)" >>/run/hosts-guard-hook.log 2>/dev/null || true
|
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@ -3,69 +3,27 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TARGET=/etc/hosts
|
# Source shared functions
|
||||||
LOGTAG=hosts-guard-hook
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=hosts-guard-common.sh
|
||||||
|
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||||
|
|
||||||
stop_units_if_present() {
|
# Remove protective attributes
|
||||||
local units=(hosts-bind-mount.service hosts-guard.path)
|
remove_host_attrs
|
||||||
for u in "${units[@]}"; do
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
|
|
||||||
systemctl stop "$u" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; }
|
|
||||||
|
|
||||||
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; }
|
|
||||||
cleanup_mount_stacks() {
|
|
||||||
local i=0
|
|
||||||
# Unmount bind layers until /etc/hosts is no longer a mountpoint
|
|
||||||
if command -v mountpoint >/dev/null 2>&1; then
|
|
||||||
while mountpoint -q "$TARGET"; do
|
|
||||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
|
||||||
i=$((i + 1))
|
|
||||||
((i > 20)) && break
|
|
||||||
done
|
|
||||||
else
|
|
||||||
# Fallback to best-effort using mountinfo count
|
|
||||||
local cnt
|
|
||||||
cnt=$(mount_layers_count)
|
|
||||||
while ((cnt > 1)); do
|
|
||||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
|
||||||
i=$((i + 1))
|
|
||||||
((i > 20)) && break
|
|
||||||
cnt=$(mount_layers_count)
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Drop protective attributes if present
|
|
||||||
if command -v lsattr >/dev/null 2>&1; then
|
|
||||||
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
|
|
||||||
if echo "$attrs" | grep -q " i "; then
|
|
||||||
chattr -i "$TARGET" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
if echo "$attrs" | grep -q " a "; then
|
|
||||||
chattr -a "$TARGET" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
# Stop guard services
|
||||||
stop_units_if_present
|
stop_units_if_present
|
||||||
|
|
||||||
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)"
|
log_hook "pre" "unlocking(start)"
|
||||||
echo "$(date -Is) pre-unlock" >>/run/hosts-guard-hook.log 2>/dev/null || true
|
|
||||||
|
|
||||||
# Always collapse any existing layers; we'll operate on the plain file
|
# Collapse any existing mount layers
|
||||||
cleanup_mount_stacks
|
collapse_mounts
|
||||||
|
|
||||||
# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again
|
# Ensure writable by remounting if still read-only
|
||||||
if is_ro_mount; then
|
if is_ro_mount; then
|
||||||
mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks
|
mount -o remount,rw "$TARGET" >/dev/null 2>&1 || collapse_mounts
|
||||||
fi
|
fi
|
||||||
|
|
||||||
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)"
|
log_hook "pre" "unlocking(done)"
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@ -50,7 +50,7 @@ extract_custom_entries_from_script() {
|
|||||||
# Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker)
|
# Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker)
|
||||||
extract_custom_entries_from_hosts() {
|
extract_custom_entries_from_hosts() {
|
||||||
local hosts_file="$1"
|
local hosts_file="$1"
|
||||||
if [[ ! -f "$hosts_file" ]]; then
|
if [[ ! -f $hosts_file ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" |
|
sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" |
|
||||||
@ -61,7 +61,7 @@ extract_custom_entries_from_hosts() {
|
|||||||
|
|
||||||
# Load previously saved custom entries state
|
# Load previously saved custom entries state
|
||||||
load_saved_custom_entries() {
|
load_saved_custom_entries() {
|
||||||
if [[ -f "$CUSTOM_ENTRIES_STATE_FILE" ]]; then
|
if [[ -f $CUSTOM_ENTRIES_STATE_FILE ]]; then
|
||||||
sort -u "$CUSTOM_ENTRIES_STATE_FILE"
|
sort -u "$CUSTOM_ENTRIES_STATE_FILE"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ save_custom_entries_state() {
|
|||||||
# Helper function to count non-empty lines
|
# Helper function to count non-empty lines
|
||||||
count_lines() {
|
count_lines() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
if [[ -z "$input" ]]; then
|
if [[ -z $input ]]; then
|
||||||
echo 0
|
echo 0
|
||||||
else
|
else
|
||||||
echo "$input" | grep -c . 2>/dev/null || echo 0
|
echo "$input" | grep -c . 2>/dev/null || echo 0
|
||||||
@ -98,7 +98,7 @@ check_custom_entries_protection() {
|
|||||||
# Get saved/existing entries (prefer state file, fall back to current /etc/hosts)
|
# Get saved/existing entries (prefer state file, fall back to current /etc/hosts)
|
||||||
local saved_entries
|
local saved_entries
|
||||||
saved_entries=$(load_saved_custom_entries)
|
saved_entries=$(load_saved_custom_entries)
|
||||||
if [[ -z "$saved_entries" ]]; then
|
if [[ -z $saved_entries ]]; then
|
||||||
# First run or state file missing - extract from current /etc/hosts if it has our marker
|
# First run or state file missing - extract from current /etc/hosts if it has our marker
|
||||||
saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts")
|
saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts")
|
||||||
fi
|
fi
|
||||||
|
|||||||
1793
report/jscpd-report.json
Normal file
1793
report/jscpd-report.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -120,6 +120,48 @@ declare -A SERVICE_STATUS
|
|||||||
ISSUES_FOUND=0
|
ISSUES_FOUND=0
|
||||||
FIXES_APPLIED=0
|
FIXES_APPLIED=0
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Report issues and optionally run fix script
|
||||||
|
# Usage: report_and_fix issues_array status_var status_key fix_note setup_script verify_service [args...]
|
||||||
|
######################################################################
|
||||||
|
report_and_fix() {
|
||||||
|
local -n _issues=$1
|
||||||
|
local -n _status=$2
|
||||||
|
local status_key="$3"
|
||||||
|
local fix_note="$4"
|
||||||
|
local setup_script="$5"
|
||||||
|
local verify_service="${6:-}"
|
||||||
|
shift 6
|
||||||
|
local script_args=("$@")
|
||||||
|
|
||||||
|
if [[ $_status != "ok" ]]; then
|
||||||
|
for issue in "${_issues[@]}"; do
|
||||||
|
if [[ $_status == "error" ]]; then
|
||||||
|
err "$issue"
|
||||||
|
else
|
||||||
|
warn "$issue"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
((ISSUES_FOUND++)) || true
|
||||||
|
|
||||||
|
if [[ $STATUS_ONLY -eq 0 && $_status == "error" ]]; then
|
||||||
|
note "$fix_note"
|
||||||
|
if [[ -f $setup_script ]]; then
|
||||||
|
run bash "$setup_script" "${script_args[@]}"
|
||||||
|
((FIXES_APPLIED++)) || true
|
||||||
|
# Re-verify after fix
|
||||||
|
if [[ $DRY_RUN -eq 0 && -n $verify_service ]] && systemctl is-enabled "$verify_service" &>/dev/null; then
|
||||||
|
_status="ok"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
err "Setup script not found: $setup_script"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERVICE_STATUS["$status_key"]=$_status
|
||||||
|
}
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Check functions
|
# Check functions
|
||||||
######################################################################
|
######################################################################
|
||||||
@ -134,7 +176,7 @@ check_pacman_wrapper() {
|
|||||||
if [[ -L /usr/bin/pacman ]]; then
|
if [[ -L /usr/bin/pacman ]]; then
|
||||||
local target
|
local target
|
||||||
target=$(readlink -f /usr/bin/pacman)
|
target=$(readlink -f /usr/bin/pacman)
|
||||||
if [[ "$target" == "/usr/local/bin/pacman_wrapper" ]]; then
|
if [[ $target == "/usr/local/bin/pacman_wrapper" ]]; then
|
||||||
msg "Pacman symlink points to wrapper"
|
msg "Pacman symlink points to wrapper"
|
||||||
else
|
else
|
||||||
issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)")
|
issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)")
|
||||||
@ -171,7 +213,7 @@ check_pacman_wrapper() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Report and fix
|
# Report and fix
|
||||||
if [[ "$status" == "error" ]]; then
|
if [[ $status == "error" ]]; then
|
||||||
for issue in "${issues[@]}"; do
|
for issue in "${issues[@]}"; do
|
||||||
err "$issue"
|
err "$issue"
|
||||||
done
|
done
|
||||||
@ -179,7 +221,7 @@ check_pacman_wrapper() {
|
|||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 ]]; then
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
||||||
note "Installing pacman wrapper..."
|
note "Installing pacman wrapper..."
|
||||||
if [[ -f "$PACMAN_WRAPPER_INSTALL" ]]; then
|
if [[ -f $PACMAN_WRAPPER_INSTALL ]]; then
|
||||||
run bash "$PACMAN_WRAPPER_INSTALL"
|
run bash "$PACMAN_WRAPPER_INSTALL"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
# Re-verify after fix
|
# Re-verify after fix
|
||||||
@ -232,33 +274,11 @@ check_midnight_shutdown() {
|
|||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Report and fix
|
report_and_fix issues status "midnight_shutdown" \
|
||||||
if [[ "$status" != "ok" ]]; then
|
"Setting up midnight shutdown..." \
|
||||||
for issue in "${issues[@]}"; do
|
"$MIDNIGHT_SHUTDOWN_SCRIPT" \
|
||||||
if [[ "$status" == "error" ]]; then
|
"day-specific-shutdown.timer" \
|
||||||
err "$issue"
|
enable
|
||||||
else
|
|
||||||
warn "$issue"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
((ISSUES_FOUND++)) || true
|
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then
|
|
||||||
note "Setting up midnight shutdown..."
|
|
||||||
if [[ -f "$MIDNIGHT_SHUTDOWN_SCRIPT" ]]; then
|
|
||||||
run bash "$MIDNIGHT_SHUTDOWN_SCRIPT" enable
|
|
||||||
((FIXES_APPLIED++)) || true
|
|
||||||
# Re-verify after fix
|
|
||||||
if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
|
|
||||||
status="ok"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
err "Setup script not found: $MIDNIGHT_SHUTDOWN_SCRIPT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERVICE_STATUS["midnight_shutdown"]=$status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_startup_monitor() {
|
check_startup_monitor() {
|
||||||
@ -298,33 +318,10 @@ check_startup_monitor() {
|
|||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Report and fix
|
report_and_fix issues status "startup_monitor" \
|
||||||
if [[ "$status" != "ok" ]]; then
|
"Setting up startup monitor..." \
|
||||||
for issue in "${issues[@]}"; do
|
"$STARTUP_MONITOR_SCRIPT" \
|
||||||
if [[ "$status" == "error" ]]; then
|
"pc-startup-monitor.timer"
|
||||||
err "$issue"
|
|
||||||
else
|
|
||||||
warn "$issue"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
((ISSUES_FOUND++)) || true
|
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then
|
|
||||||
note "Setting up startup monitor..."
|
|
||||||
if [[ -f "$STARTUP_MONITOR_SCRIPT" ]]; then
|
|
||||||
run bash "$STARTUP_MONITOR_SCRIPT"
|
|
||||||
((FIXES_APPLIED++)) || true
|
|
||||||
# Re-verify after fix
|
|
||||||
if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
|
|
||||||
status="ok"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
err "Setup script not found: $STARTUP_MONITOR_SCRIPT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERVICE_STATUS["startup_monitor"]=$status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_periodic_systems() {
|
check_periodic_systems() {
|
||||||
@ -379,33 +376,10 @@ check_periodic_systems() {
|
|||||||
status="error"
|
status="error"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Report and fix
|
report_and_fix issues status "periodic_systems" \
|
||||||
if [[ "$status" != "ok" ]]; then
|
"Setting up periodic systems..." \
|
||||||
for issue in "${issues[@]}"; do
|
"$PERIODIC_SYSTEM_SCRIPT" \
|
||||||
if [[ "$status" == "error" ]]; then
|
"periodic-system-maintenance.timer"
|
||||||
err "$issue"
|
|
||||||
else
|
|
||||||
warn "$issue"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
((ISSUES_FOUND++)) || true
|
|
||||||
|
|
||||||
if [[ $STATUS_ONLY -eq 0 && "$status" == "error" ]]; then
|
|
||||||
note "Setting up periodic systems..."
|
|
||||||
if [[ -f "$PERIODIC_SYSTEM_SCRIPT" ]]; then
|
|
||||||
run bash "$PERIODIC_SYSTEM_SCRIPT"
|
|
||||||
((FIXES_APPLIED++)) || true
|
|
||||||
# Re-verify after fix
|
|
||||||
if [[ $DRY_RUN -eq 0 ]] && systemctl is-enabled periodic-system-maintenance.timer &>/dev/null; then
|
|
||||||
status="ok"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
err "Setup script not found: $PERIODIC_SYSTEM_SCRIPT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERVICE_STATUS["periodic_systems"]=$status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check_hosts() {
|
check_hosts() {
|
||||||
@ -432,7 +406,7 @@ check_hosts() {
|
|||||||
# Check if hosts file is immutable
|
# Check if hosts file is immutable
|
||||||
local attrs
|
local attrs
|
||||||
attrs=$(lsattr /etc/hosts 2>/dev/null | cut -d' ' -f1 || echo "")
|
attrs=$(lsattr /etc/hosts 2>/dev/null | cut -d' ' -f1 || echo "")
|
||||||
if [[ "$attrs" == *"i"* ]]; then
|
if [[ $attrs == *"i"* ]]; then
|
||||||
msg "/etc/hosts has immutable attribute set"
|
msg "/etc/hosts has immutable attribute set"
|
||||||
else
|
else
|
||||||
issues+=("/etc/hosts is not immutable")
|
issues+=("/etc/hosts is not immutable")
|
||||||
@ -503,9 +477,9 @@ check_hosts() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Report issues
|
# Report issues
|
||||||
if [[ "$status" != "ok" ]]; then
|
if [[ $status != "ok" ]]; then
|
||||||
for issue in "${issues[@]}"; do
|
for issue in "${issues[@]}"; do
|
||||||
if [[ "$status" == "error" ]]; then
|
if [[ $status == "error" ]]; then
|
||||||
err "$issue"
|
err "$issue"
|
||||||
else
|
else
|
||||||
warn "$issue"
|
warn "$issue"
|
||||||
@ -517,7 +491,7 @@ check_hosts() {
|
|||||||
# Run hosts install first
|
# Run hosts install first
|
||||||
if [[ ! -f /etc/hosts ]] || [[ $(wc -l </etc/hosts) -lt 100 ]]; then
|
if [[ ! -f /etc/hosts ]] || [[ $(wc -l </etc/hosts) -lt 100 ]]; then
|
||||||
note "Installing hosts file..."
|
note "Installing hosts file..."
|
||||||
if [[ -f "$HOSTS_INSTALL_SCRIPT" ]]; then
|
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_INSTALL_SCRIPT"
|
run bash "$HOSTS_INSTALL_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
@ -528,7 +502,7 @@ check_hosts() {
|
|||||||
# Run hosts guard setup
|
# Run hosts guard setup
|
||||||
if ! systemctl is-enabled hosts-guard.path &>/dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then
|
if ! systemctl is-enabled hosts-guard.path &>/dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then
|
||||||
note "Setting up hosts guard..."
|
note "Setting up hosts guard..."
|
||||||
if [[ -f "$HOSTS_GUARD_SCRIPT" ]]; then
|
if [[ -f $HOSTS_GUARD_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_GUARD_SCRIPT"
|
run bash "$HOSTS_GUARD_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
@ -539,7 +513,7 @@ check_hosts() {
|
|||||||
# Install pacman hooks if missing
|
# Install pacman hooks if missing
|
||||||
if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then
|
||||||
note "Installing pacman hooks..."
|
note "Installing pacman hooks..."
|
||||||
if [[ -f "$HOSTS_PACMAN_HOOKS_SCRIPT" ]]; then
|
if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then
|
||||||
run bash "$HOSTS_PACMAN_HOOKS_SCRIPT"
|
run bash "$HOSTS_PACMAN_HOOKS_SCRIPT"
|
||||||
((FIXES_APPLIED++)) || true
|
((FIXES_APPLIED++)) || true
|
||||||
else
|
else
|
||||||
|
|||||||
@ -66,10 +66,10 @@ was_opened_this_hour() {
|
|||||||
local current_hour
|
local current_hour
|
||||||
current_hour=$(get_hour_key)
|
current_hour=$(get_hour_key)
|
||||||
|
|
||||||
if [[ -f "$state_file" ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
local last_hour
|
local last_hour
|
||||||
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
|
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
|
||||||
if [[ "$last_hour" == "$current_hour" ]]; then
|
if [[ $last_hour == "$current_hour" ]]; then
|
||||||
return 0 # Was opened this hour
|
return 0 # Was opened this hour
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -152,13 +152,13 @@ install_wrapper() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if wrapper location exists (file or symlink)
|
# Check if wrapper location exists (file or symlink)
|
||||||
if [[ ! -e "$wrapper_path" && ! -L "$wrapper_path" ]]; then
|
if [[ ! -e $wrapper_path && ! -L $wrapper_path ]]; then
|
||||||
echo " ⚠ $app not installed ($wrapper_path not found)"
|
echo " ⚠ $app not installed ($wrapper_path not found)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if real binary exists
|
# Check if real binary exists
|
||||||
if [[ ! -x "$real_binary" ]]; then
|
if [[ ! -x $real_binary ]]; then
|
||||||
echo " ⚠ $app real binary not found ($real_binary)"
|
echo " ⚠ $app real binary not found ($real_binary)"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@ -166,7 +166,7 @@ install_wrapper() {
|
|||||||
echo " Installing wrapper for $app..."
|
echo " Installing wrapper for $app..."
|
||||||
|
|
||||||
# Handle symlinks: save the symlink itself, not the target
|
# Handle symlinks: save the symlink itself, not the target
|
||||||
if [[ -L "$wrapper_path" ]]; then
|
if [[ -L $wrapper_path ]]; then
|
||||||
local link_target
|
local link_target
|
||||||
link_target=$(readlink "$wrapper_path")
|
link_target=$(readlink "$wrapper_path")
|
||||||
echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig"
|
echo " Saving symlink $wrapper_path -> $link_target as ${wrapper_path}.orig"
|
||||||
@ -207,7 +207,7 @@ uninstall_wrapper() {
|
|||||||
# Check if it was a symlink (stored as SYMLINK:target in .orig)
|
# Check if it was a symlink (stored as SYMLINK:target in .orig)
|
||||||
local orig_content
|
local orig_content
|
||||||
orig_content=$(cat "${wrapper_path}.orig" 2>/dev/null || echo "")
|
orig_content=$(cat "${wrapper_path}.orig" 2>/dev/null || echo "")
|
||||||
if [[ "$orig_content" == SYMLINK:* ]]; then
|
if [[ $orig_content == SYMLINK:* ]]; then
|
||||||
local link_target="${orig_content#SYMLINK:}"
|
local link_target="${orig_content#SYMLINK:}"
|
||||||
echo " Restoring symlink $wrapper_path -> $link_target"
|
echo " Restoring symlink $wrapper_path -> $link_target"
|
||||||
ln -s "$link_target" "$wrapper_path"
|
ln -s "$link_target" "$wrapper_path"
|
||||||
@ -229,7 +229,7 @@ install_all() {
|
|||||||
script_path="$(readlink -f "$0")"
|
script_path="$(readlink -f "$0")"
|
||||||
local install_path="/usr/local/bin/block-compulsive-opening.sh"
|
local install_path="/usr/local/bin/block-compulsive-opening.sh"
|
||||||
|
|
||||||
if [[ "$script_path" != "$install_path" ]]; then
|
if [[ $script_path != "$install_path" ]]; then
|
||||||
echo "Installing main script to $install_path..."
|
echo "Installing main script to $install_path..."
|
||||||
cp "$script_path" "$install_path"
|
cp "$script_path" "$install_path"
|
||||||
chmod +x "$install_path"
|
chmod +x "$install_path"
|
||||||
@ -287,10 +287,10 @@ show_status() {
|
|||||||
local status="not opened this hour"
|
local status="not opened this hour"
|
||||||
local icon="○"
|
local icon="○"
|
||||||
|
|
||||||
if [[ -f "$state_file" ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
local last_hour
|
local last_hour
|
||||||
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
|
last_hour=$(cat "$state_file" 2>/dev/null || echo "")
|
||||||
if [[ "$last_hour" == "$current_hour" ]]; then
|
if [[ $last_hour == "$current_hour" ]]; then
|
||||||
status="already opened (blocked until next hour)"
|
status="already opened (blocked until next hour)"
|
||||||
icon="●"
|
icon="●"
|
||||||
else
|
else
|
||||||
@ -303,7 +303,7 @@ show_status() {
|
|||||||
local wrapper_path="${APPS[$app]}"
|
local wrapper_path="${APPS[$app]}"
|
||||||
if [[ -f "${wrapper_path}.orig" ]]; then
|
if [[ -f "${wrapper_path}.orig" ]]; then
|
||||||
wrapped="wrapped"
|
wrapped="wrapped"
|
||||||
elif [[ -f "$wrapper_path" ]]; then
|
elif [[ -f $wrapper_path ]]; then
|
||||||
wrapped="installed (not wrapped)"
|
wrapped="installed (not wrapped)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -320,7 +320,7 @@ reset_app() {
|
|||||||
local state_file
|
local state_file
|
||||||
state_file=$(get_state_file "$app")
|
state_file=$(get_state_file "$app")
|
||||||
|
|
||||||
if [[ -f "$state_file" ]]; then
|
if [[ -f $state_file ]]; then
|
||||||
rm -f "$state_file"
|
rm -f "$state_file"
|
||||||
echo "Reset $app - can be opened again this hour"
|
echo "Reset $app - can be opened again this hour"
|
||||||
log_message "RESET: $app state cleared by user"
|
log_message "RESET: $app state cleared by user"
|
||||||
@ -392,7 +392,7 @@ main() {
|
|||||||
show_status
|
show_status
|
||||||
;;
|
;;
|
||||||
reset)
|
reset)
|
||||||
if [[ -z "${2:-}" ]]; then
|
if [[ -z ${2:-} ]]; then
|
||||||
echo "Error: specify app to reset"
|
echo "Error: specify app to reset"
|
||||||
echo "Apps: ${!APPS[*]}"
|
echo "Apps: ${!APPS[*]}"
|
||||||
exit 1
|
exit 1
|
||||||
@ -403,7 +403,7 @@ main() {
|
|||||||
reset_all
|
reset_all
|
||||||
;;
|
;;
|
||||||
wrapper)
|
wrapper)
|
||||||
if [[ -z "${2:-}" ]]; then
|
if [[ -z ${2:-} ]]; then
|
||||||
echo "Error: wrapper requires app name"
|
echo "Error: wrapper requires app name"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -107,12 +107,12 @@ kill_music_services() {
|
|||||||
local yt_music_windows
|
local yt_music_windows
|
||||||
yt_music_windows=$(xdotool search --name "YouTube Music" 2>/dev/null || true)
|
yt_music_windows=$(xdotool search --name "YouTube Music" 2>/dev/null || true)
|
||||||
for wid in $yt_music_windows; do
|
for wid in $yt_music_windows; do
|
||||||
if [[ -n "$wid" ]]; then
|
if [[ -n $wid ]]; then
|
||||||
# Get window name for logging
|
# Get window name for logging
|
||||||
local wname
|
local wname
|
||||||
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
|
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
|
||||||
# Only close if it's YouTube Music, not regular YouTube
|
# Only close if it's YouTube Music, not regular YouTube
|
||||||
if [[ "$wname" == *"YouTube Music"* ]] || [[ "$wname" == *"music.youtube.com"* ]]; then
|
if [[ $wname == *"YouTube Music"* ]] || [[ $wname == *"music.youtube.com"* ]]; then
|
||||||
log_message "Closing YouTube Music window: $wname (ID: $wid)"
|
log_message "Closing YouTube Music window: $wname (ID: $wid)"
|
||||||
xdotool windowclose "$wid" 2>/dev/null || true
|
xdotool windowclose "$wid" 2>/dev/null || true
|
||||||
killed=true
|
killed=true
|
||||||
@ -152,7 +152,7 @@ kill_music_services() {
|
|||||||
local windows
|
local windows
|
||||||
windows=$(xdotool search --name "$pattern" 2>/dev/null || true)
|
windows=$(xdotool search --name "$pattern" 2>/dev/null || true)
|
||||||
for wid in $windows; do
|
for wid in $windows; do
|
||||||
if [[ -n "$wid" ]]; then
|
if [[ -n $wid ]]; then
|
||||||
local wname
|
local wname
|
||||||
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
|
wname=$(xdotool getwindowname "$wid" 2>/dev/null || echo "unknown")
|
||||||
log_message "Closing music service window: $wname (ID: $wid)"
|
log_message "Closing music service window: $wname (ID: $wid)"
|
||||||
|
|||||||
@ -220,8 +220,6 @@ function is_greylisted_package_name() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Helper: detect if current invocation includes --noconfirm
|
|
||||||
|
|
||||||
# Helper: detect if current invocation includes --noconfirm
|
# Helper: detect if current invocation includes --noconfirm
|
||||||
function has_noconfirm_flag() {
|
function has_noconfirm_flag() {
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
@ -232,6 +230,27 @@ function has_noconfirm_flag() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Helper: get list of PIDs holding a lock file (excluding our own PID)
|
||||||
|
# Populates the $holders array
|
||||||
|
get_lock_holders() {
|
||||||
|
local lock_file="$1"
|
||||||
|
holders=()
|
||||||
|
if command -v fuser >/dev/null 2>&1; then
|
||||||
|
mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
|
||||||
|
elif command -v lsof >/dev/null 2>&1; then
|
||||||
|
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true)
|
||||||
|
fi
|
||||||
|
# Filter out our own PID
|
||||||
|
if [[ ${#holders[@]} -gt 0 ]]; then
|
||||||
|
local -a filtered=()
|
||||||
|
for pid in "${holders[@]}"; do
|
||||||
|
[[ $pid -eq $$ ]] && continue
|
||||||
|
filtered+=("$pid")
|
||||||
|
done
|
||||||
|
holders=("${filtered[@]}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Handle stale pacman database lock if present and no package managers are running
|
# Handle stale pacman database lock if present and no package managers are running
|
||||||
check_and_handle_db_lock() {
|
check_and_handle_db_lock() {
|
||||||
local lock_file="/var/lib/pacman/db.lck"
|
local lock_file="/var/lib/pacman/db.lck"
|
||||||
@ -242,23 +261,7 @@ check_and_handle_db_lock() {
|
|||||||
|
|
||||||
# Determine which processes actually have the lock open
|
# Determine which processes actually have the lock open
|
||||||
local -a holders=()
|
local -a holders=()
|
||||||
if command -v fuser >/dev/null 2>&1; then
|
get_lock_holders "$lock_file"
|
||||||
mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
|
|
||||||
elif command -v lsof >/dev/null 2>&1; then
|
|
||||||
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true)
|
|
||||||
else
|
|
||||||
holders=()
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Filter out our own PID if it somehow appears
|
|
||||||
if [[ ${#holders[@]} -gt 0 ]]; then
|
|
||||||
local -a filtered=()
|
|
||||||
for pid in "${holders[@]}"; do
|
|
||||||
[[ $pid -eq $$ ]] && continue
|
|
||||||
filtered+=("$pid")
|
|
||||||
done
|
|
||||||
holders=("${filtered[@]}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#holders[@]} -gt 0 ]]; then
|
if [[ ${#holders[@]} -gt 0 ]]; then
|
||||||
local pac_holder=0
|
local pac_holder=0
|
||||||
@ -292,12 +295,7 @@ check_and_handle_db_lock() {
|
|||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
# Re-check holders
|
# Re-check holders
|
||||||
holders=()
|
get_lock_holders "$lock_file"
|
||||||
if command -v fuser >/dev/null 2>&1; then
|
|
||||||
mapfile -t holders < <(fuser "$lock_file" 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+$' || true)
|
|
||||||
elif command -v lsof >/dev/null 2>&1; then
|
|
||||||
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null | grep -E '^[0-9]+$' || true)
|
|
||||||
fi
|
|
||||||
if [[ ${#holders[@]} -gt 0 ]]; then
|
if [[ ${#holders[@]} -gt 0 ]]; then
|
||||||
echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2
|
echo -e "${RED}Cannot free the pacman lock; another process still holds it. Try again later.${NC}" >&2
|
||||||
return 1
|
return 1
|
||||||
@ -305,6 +303,16 @@ check_and_handle_db_lock() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Helper to remove a file with sudo if needed
|
||||||
|
remove_file_as_root() {
|
||||||
|
local f="$1"
|
||||||
|
if [[ $EUID -ne 0 ]]; then
|
||||||
|
sudo rm -f "$f"
|
||||||
|
else
|
||||||
|
rm -f "$f"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Decide whether to remove the lock
|
# Decide whether to remove the lock
|
||||||
local now epoch age
|
local now epoch age
|
||||||
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
|
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
|
||||||
@ -317,11 +325,7 @@ check_and_handle_db_lock() {
|
|||||||
# Auto-remove in non-interactive mode (--noconfirm) or if the lock is older than 10 minutes
|
# Auto-remove in non-interactive mode (--noconfirm) or if the lock is older than 10 minutes
|
||||||
if has_noconfirm_flag "$@" || [[ $age -ge 600 ]]; then
|
if has_noconfirm_flag "$@" || [[ $age -ge 600 ]]; then
|
||||||
echo -e "${YELLOW}Stale pacman lock detected (age: ${age}s). Removing it automatically...${NC}" >&2
|
echo -e "${YELLOW}Stale pacman lock detected (age: ${age}s). Removing it automatically...${NC}" >&2
|
||||||
if [[ $EUID -ne 0 ]]; then
|
remove_file_as_root "$lock_file" || return 1
|
||||||
sudo rm -f "$lock_file" || return 1
|
|
||||||
else
|
|
||||||
rm -f "$lock_file" || return 1
|
|
||||||
fi
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -330,25 +334,23 @@ check_and_handle_db_lock() {
|
|||||||
echo -e "${CYAN}Lock path:${NC} $lock_file (age: ${age}s)" >&2
|
echo -e "${CYAN}Lock path:${NC} $lock_file (age: ${age}s)" >&2
|
||||||
read -r -t 15 -p $'Remove stale lock and continue? [y/N]: ' reply || reply="n"
|
read -r -t 15 -p $'Remove stale lock and continue? [y/N]: ' reply || reply="n"
|
||||||
if [[ ${reply,,} == "y" || ${reply,,} == "yes" ]]; then
|
if [[ ${reply,,} == "y" || ${reply,,} == "yes" ]]; then
|
||||||
if [[ $EUID -ne 0 ]]; then
|
remove_file_as_root "$lock_file" || return 1
|
||||||
sudo rm -f "$lock_file" || return 1
|
|
||||||
else
|
|
||||||
rm -f "$lock_file" || return 1
|
|
||||||
fi
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
echo -e "${RED}Aborting due to existing pacman lock. Close other updaters and retry, or run with --noconfirm to auto-clear stale locks.${NC}" >&2
|
echo -e "${RED}Aborting due to existing pacman lock. Close other updaters and retry, or run with --noconfirm to auto-clear stale locks.${NC}" >&2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cleanup: remove any installed blocked packages (in addition to the queued operation)
|
# Generic function to remove installed packages matching a filter
|
||||||
function remove_installed_blocked_packages() {
|
# Args: check_function label_prefix
|
||||||
# args not used; kept for future policy extension
|
function remove_installed_packages_matching() {
|
||||||
# List installed package names
|
local check_function="$1"
|
||||||
|
local label="$2"
|
||||||
|
|
||||||
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
|
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
|
||||||
local to_remove=()
|
local to_remove=()
|
||||||
for name in "${installed_names[@]}"; do
|
for name in "${installed_names[@]}"; do
|
||||||
if is_blocked_package_name "$name"; then
|
if "$check_function" "$name"; then
|
||||||
to_remove+=("$name")
|
to_remove+=("$name")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -357,83 +359,59 @@ function remove_installed_blocked_packages() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${YELLOW}Policy cleanup:${NC} Removing blocked installed packages: ${BOLD}${to_remove[*]}${NC}" >&2
|
echo -e "${YELLOW}${label} cleanup:${NC} Removing packages: ${BOLD}${to_remove[*]}${NC}" >&2
|
||||||
local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm)
|
"$PACMAN_BIN" -Rns --noconfirm "${to_remove[@]}"
|
||||||
"${remove_cmd[@]}" "${to_remove[@]}"
|
|
||||||
local rc=$?
|
local rc=$?
|
||||||
if [[ $rc -ne 0 ]]; then
|
if [[ $rc -ne 0 ]]; then
|
||||||
echo -e "${RED}Cleanup removal failed with exit code ${rc}.${NC}" >&2
|
echo -e "${RED}${label} cleanup removal failed with exit code ${rc}.${NC}" >&2
|
||||||
else
|
else
|
||||||
echo -e "${GREEN}Cleanup removal completed for: ${to_remove[*]}${NC}" >&2
|
echo -e "${GREEN}${label} cleanup removal completed for: ${to_remove[*]}${NC}" >&2
|
||||||
fi
|
fi
|
||||||
return $rc
|
return $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cleanup: remove any installed greylisted packages (challenge-required packages)
|
# Cleanup: remove any installed blocked packages
|
||||||
function remove_installed_greylisted_packages() {
|
function remove_installed_blocked_packages() {
|
||||||
# List installed package names
|
remove_installed_packages_matching is_blocked_package_name "Policy"
|
||||||
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
|
}
|
||||||
local to_remove=()
|
|
||||||
for name in "${installed_names[@]}"; do
|
|
||||||
if is_greylisted_package_name "$name"; then
|
|
||||||
to_remove+=("$name")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${#to_remove[@]} -eq 0 ]]; then
|
# Cleanup: remove any installed greylisted packages
|
||||||
|
function remove_installed_greylisted_packages() {
|
||||||
|
remove_installed_packages_matching is_greylisted_package_name "Greylist"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper: Check if this is an install command and run a filter on each package name
|
||||||
|
# Usage: check_install_for filter_func "$@"
|
||||||
|
# Returns 0 if filter_func matches any package
|
||||||
|
function check_install_for() {
|
||||||
|
local filter_func="$1"
|
||||||
|
shift
|
||||||
|
# Check if the command is an installation command
|
||||||
|
if [[ ${1:-} == "-S" || ${1:-} == "-Sy" || ${1:-} == "-Syu" || ${1:-} == "-Syyu" || ${1:-} == "-U" ]]; then
|
||||||
|
for arg in "$@"; do
|
||||||
|
# Strip repository prefix if present (like extra/ or community/)
|
||||||
|
local package_name="${arg##*/}"
|
||||||
|
if "$filter_func" "$package_name"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
echo -e "${YELLOW}Greylist cleanup:${NC} Removing greylisted installed packages: ${BOLD}${to_remove[*]}${NC}" >&2
|
|
||||||
local remove_cmd=("$PACMAN_BIN" -Rns --noconfirm)
|
|
||||||
"${remove_cmd[@]}" "${to_remove[@]}"
|
|
||||||
local rc=$?
|
|
||||||
if [[ $rc -ne 0 ]]; then
|
|
||||||
echo -e "${RED}Greylist cleanup removal failed with exit code ${rc}.${NC}" >&2
|
|
||||||
else
|
|
||||||
echo -e "${GREEN}Greylist cleanup removal completed for: ${to_remove[*]}${NC}" >&2
|
|
||||||
fi
|
fi
|
||||||
return $rc
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if user is trying to install packages that are always blocked
|
# Function to check if user is trying to install packages that are always blocked
|
||||||
function check_for_always_blocked() {
|
function check_for_always_blocked() {
|
||||||
# Check if the command is an installation command
|
check_install_for is_blocked_package_name "$@"
|
||||||
if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then
|
}
|
||||||
# Check all arguments
|
|
||||||
for arg in "$@"; do
|
# Helper to check if a package name is steam
|
||||||
# Strip repository prefix if present (like extra/ or community/)
|
function is_steam_package() {
|
||||||
local package_name="${arg##*/}"
|
[[ $1 == "steam" ]]
|
||||||
if is_blocked_package_name "$package_name"; then
|
|
||||||
return 0 # Always blocked package found
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
return 1 # No always blocked package found
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if user is trying to install steam (challenge-eligible package)
|
# Function to check if user is trying to install steam (challenge-eligible package)
|
||||||
function check_for_steam() {
|
function check_for_steam() {
|
||||||
# List of packages that require challenge (only steam in this case)
|
check_install_for is_steam_package "$@"
|
||||||
local steam_packages=("steam")
|
|
||||||
|
|
||||||
# Check if the command is an installation command
|
|
||||||
if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then
|
|
||||||
# Check all arguments
|
|
||||||
for arg in "$@"; do
|
|
||||||
# Strip repository prefix if present (like extra/ or community/)
|
|
||||||
local package_name="${arg##*/}"
|
|
||||||
|
|
||||||
# Check if argument matches steam
|
|
||||||
for package in "${steam_packages[@]}"; do
|
|
||||||
if [[ $arg == "$package" || $arg == *"/$package-"* || $arg == *"/$package/"* ||
|
|
||||||
$arg == *"/$package" || $package_name == "$package" ]]; then
|
|
||||||
return 0 # Steam package found
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
return 1 # No steam package found
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if current day is a weekday (after 4PM Friday until midnight Sunday)
|
# Function to check if current day is a weekday (after 4PM Friday until midnight Sunday)
|
||||||
@ -459,6 +437,121 @@ function is_weekday() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Unified word unscrambling challenge function
|
||||||
|
# Args: challenge_name word_length words_count timeout_seconds initial_delay_max post_delay_min post_delay_range
|
||||||
|
function run_word_challenge() {
|
||||||
|
local challenge_name="$1"
|
||||||
|
local word_length="$2"
|
||||||
|
local words_count="$3"
|
||||||
|
local timeout_seconds="$4"
|
||||||
|
local initial_delay_max="${5:-20}"
|
||||||
|
local post_delay_min="${6:-0}"
|
||||||
|
local post_delay_range="${7:-20}"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}${challenge_name} challenge will begin shortly...${NC}"
|
||||||
|
|
||||||
|
# Initial delay
|
||||||
|
local sleep_duration=$((RANDOM % initial_delay_max))
|
||||||
|
sleep "$sleep_duration"
|
||||||
|
|
||||||
|
# Load words file
|
||||||
|
local script_dir words_file
|
||||||
|
script_dir="$(dirname "$(readlink -f "$0")")"
|
||||||
|
words_file="$script_dir/words.txt"
|
||||||
|
|
||||||
|
if [[ ! -f $words_file ]]; then
|
||||||
|
echo -e "${RED}Error: words.txt file not found at $words_file${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${CYAN}Challenge: Words with ${word_length} letters${NC}"
|
||||||
|
|
||||||
|
# Load random words of specified length
|
||||||
|
local -a selected_words
|
||||||
|
mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count")
|
||||||
|
|
||||||
|
if [[ ${#selected_words[@]} -lt $words_count ]]; then
|
||||||
|
echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}"
|
||||||
|
words_count=${#selected_words[@]}
|
||||||
|
if [[ $words_count -eq 0 ]]; then
|
||||||
|
echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Convert to uppercase
|
||||||
|
for i in "${!selected_words[@]}"; do
|
||||||
|
selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]')
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}"
|
||||||
|
|
||||||
|
# Display words in grid
|
||||||
|
for ((i = 0; i < words_count; i++)); do
|
||||||
|
printf "${BLUE}%-15s${NC}" "${selected_words[i]}"
|
||||||
|
if (((i + 1) % 4 == 0)); then
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Select and scramble a word
|
||||||
|
local target_index target_word scrambled_word
|
||||||
|
target_index=$((RANDOM % words_count))
|
||||||
|
target_word="${selected_words[target_index]}"
|
||||||
|
scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n')
|
||||||
|
|
||||||
|
if [[ $scrambled_word == "$target_word" ]]; then
|
||||||
|
scrambled_word=$(echo "$target_word" | rev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}"
|
||||||
|
echo -e "${YELLOW}Unscramble the word to proceed (you have $timeout_seconds seconds):${NC}"
|
||||||
|
|
||||||
|
# Timer display background process
|
||||||
|
(
|
||||||
|
local start_time current_time elapsed remaining
|
||||||
|
start_time=$(date +%s)
|
||||||
|
while true; do
|
||||||
|
current_time=$(date +%s)
|
||||||
|
elapsed=$((current_time - start_time))
|
||||||
|
remaining=$((timeout_seconds - elapsed))
|
||||||
|
if [[ $remaining -le 0 ]]; then
|
||||||
|
echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} "
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} "
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
local display_pid=$!
|
||||||
|
|
||||||
|
# Read input with timeout
|
||||||
|
local user_input read_status
|
||||||
|
read -t "$timeout_seconds" -r user_input
|
||||||
|
read_status=$?
|
||||||
|
|
||||||
|
kill "$display_pid" 2>/dev/null
|
||||||
|
wait "$display_pid" 2>/dev/null
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ $read_status -ne 0 ]]; then
|
||||||
|
echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs)
|
||||||
|
|
||||||
|
if [[ $user_input == "$target_word" ]]; then
|
||||||
|
echo -e "${GREEN}Correct! Proceeding with installation...${NC}"
|
||||||
|
local post_challenge_sleep=$((RANDOM % post_delay_range + post_delay_min))
|
||||||
|
[[ $post_challenge_sleep -gt 0 ]] && sleep "$post_challenge_sleep"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to prompt for solving a word unscrambling challenge (only for steam)
|
# Function to prompt for solving a word unscrambling challenge (only for steam)
|
||||||
function prompt_for_steam_challenge() {
|
function prompt_for_steam_challenge() {
|
||||||
echo -e "${YELLOW}WARNING: You are trying to install Steam.${NC}"
|
echo -e "${YELLOW}WARNING: You are trying to install Steam.${NC}"
|
||||||
@ -472,259 +565,20 @@ function prompt_for_steam_challenge() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${YELLOW}Weekend Steam challenge will begin shortly...${NC}"
|
# word_length=5, words_count=160, timeout=60s, initial_delay=20, post_delay=0-20
|
||||||
|
run_word_challenge "Weekend Steam" 5 160 60 20 0 20
|
||||||
# Sleep for random 20-40 seconds
|
|
||||||
# sleep_duration=$((RANDOM % 20 + 20))
|
|
||||||
sleep_duration=$((RANDOM % 20))
|
|
||||||
sleep "$sleep_duration"
|
|
||||||
|
|
||||||
# Define path to words.txt (in the same directory as the script)
|
|
||||||
script_dir="$(dirname "$(readlink -f "$0")")"
|
|
||||||
words_file="$script_dir/words.txt"
|
|
||||||
|
|
||||||
# Check if words.txt exists
|
|
||||||
if [[ ! -f $words_file ]]; then
|
|
||||||
echo -e "${RED}Error: words.txt file not found at $words_file${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Choose a specific word length (5, 6, 7, or 8 characters)
|
|
||||||
#
|
|
||||||
word_length=5
|
|
||||||
echo -e "${CYAN}Today's challenge: Words with ${word_length} letters${NC}"
|
|
||||||
|
|
||||||
# Filter words by the specific chosen length and load random words
|
|
||||||
words_count=160
|
|
||||||
mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count")
|
|
||||||
|
|
||||||
# If we couldn't get enough words of the right length
|
|
||||||
if [[ ${#selected_words[@]} -lt $words_count ]]; then
|
|
||||||
echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}"
|
|
||||||
words_count=${#selected_words[@]}
|
|
||||||
if [[ $words_count -eq 0 ]]; then
|
|
||||||
echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Convert all words to uppercase
|
|
||||||
for i in "${!selected_words[@]}"; do
|
|
||||||
selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]')
|
|
||||||
done
|
|
||||||
|
|
||||||
echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}"
|
|
||||||
|
|
||||||
# Display the words in a grid (4 columns)
|
|
||||||
for ((i = 0; i < words_count; i++)); do
|
|
||||||
printf "${BLUE}%-15s${NC}" "${selected_words[i]}"
|
|
||||||
if (((i + 1) % 4 == 0)); then
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Select a random word to scramble (already in uppercase)
|
|
||||||
target_index=$((RANDOM % words_count))
|
|
||||||
target_word="${selected_words[target_index]}"
|
|
||||||
|
|
||||||
# Scramble the word
|
|
||||||
scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n')
|
|
||||||
|
|
||||||
# Ensure scrambled word is different from original
|
|
||||||
if [[ $scrambled_word == "$target_word" ]]; then
|
|
||||||
# Use simple reversal as fallback
|
|
||||||
scrambled_word=$(echo "$target_word" | rev)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}"
|
|
||||||
echo -e "${YELLOW}Unscramble the word to proceed with installation (you have 2 minutes):${NC}"
|
|
||||||
|
|
||||||
# Set up a background process to display the timer
|
|
||||||
(
|
|
||||||
start_time=$(date +%s)
|
|
||||||
while true; do
|
|
||||||
current_time=$(date +%s)
|
|
||||||
elapsed=$((current_time - start_time))
|
|
||||||
remaining=$((60 - elapsed))
|
|
||||||
|
|
||||||
if [[ $remaining -le 0 ]]; then
|
|
||||||
echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} "
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} "
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
display_pid=$!
|
|
||||||
|
|
||||||
# Read user input with timeout
|
|
||||||
read -t 60 -r user_input
|
|
||||||
read_status=$?
|
|
||||||
|
|
||||||
# Kill the timer display
|
|
||||||
kill "$display_pid" 2>/dev/null
|
|
||||||
wait "$display_pid" 2>/dev/null
|
|
||||||
echo # Add a newline after the timer
|
|
||||||
|
|
||||||
# Check if read timed out
|
|
||||||
if [[ $read_status -ne 0 ]]; then
|
|
||||||
echo -e "${RED}Time's up! Challenge failed. The correct word was '$target_word'.${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Convert user input to uppercase and trim whitespaces
|
|
||||||
user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs)
|
|
||||||
|
|
||||||
if [[ $user_input == "$target_word" ]]; then
|
|
||||||
echo -e "${GREEN}Correct! Proceeding with installation...${NC}"
|
|
||||||
|
|
||||||
# Add sleep after successful challenge completion (20-40 seconds)
|
|
||||||
# post_challenge_sleep=$((RANDOM % 20 + 20))
|
|
||||||
post_challenge_sleep=$((RANDOM % 20))
|
|
||||||
sleep "$post_challenge_sleep"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_for_greylisted() {
|
function check_for_greylisted() {
|
||||||
# Check if the command is an installation command
|
check_install_for is_greylisted_package_name "$@"
|
||||||
if [[ $1 == "-S" || $1 == "-Sy" || $1 == "-Syu" || $1 == "-Syyu" || $1 == "-U" ]]; then
|
|
||||||
# Check all arguments
|
|
||||||
for arg in "$@"; do
|
|
||||||
# Strip repository prefix if present
|
|
||||||
local package_name="${arg##*/}"
|
|
||||||
|
|
||||||
# Check if package name matches any greylisted keyword
|
|
||||||
if is_greylisted_package_name "$package_name"; then
|
|
||||||
return 0 # Greylisted package found
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
return 1 # No greylisted package found
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to prompt for solving a word unscrambling challenge (for greylisted packages - always active)
|
# Function to prompt for solving a word unscrambling challenge (for greylisted packages - always active)
|
||||||
function prompt_for_greylist_challenge() {
|
function prompt_for_greylist_challenge() {
|
||||||
echo -e "${YELLOW}WARNING: You are trying to install a greylisted package.${NC}"
|
echo -e "${YELLOW}WARNING: You are trying to install a greylisted package.${NC}"
|
||||||
echo -e "${YELLOW}Greylist challenge will begin shortly...${NC}"
|
|
||||||
|
|
||||||
# Sleep for random 10-30 seconds
|
# word_length=6, words_count=120, timeout=90s, initial_delay=30, post_delay=15-35
|
||||||
sleep_duration=$((RANDOM % 20 + 10))
|
run_word_challenge "Greylist" 6 120 90 30 15 20
|
||||||
sleep "$sleep_duration"
|
|
||||||
|
|
||||||
# Define path to words.txt (in the same directory as the script)
|
|
||||||
script_dir="$(dirname "$(readlink -f "$0")")"
|
|
||||||
words_file="$script_dir/words.txt"
|
|
||||||
|
|
||||||
# Check if words.txt exists
|
|
||||||
if [[ ! -f $words_file ]]; then
|
|
||||||
echo -e "${RED}Error: words.txt file not found at $words_file${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Choose a specific word length (6 characters for greylist challenge)
|
|
||||||
word_length=6
|
|
||||||
echo -e "${CYAN}Greylist challenge: Words with ${word_length} letters${NC}"
|
|
||||||
|
|
||||||
# Filter words by the specific chosen length and load random words
|
|
||||||
words_count=120
|
|
||||||
mapfile -t selected_words < <(grep -E "^[a-zA-Z]{$word_length}$" "$words_file" | shuf -n "$words_count")
|
|
||||||
|
|
||||||
# If we couldn't get enough words of the right length
|
|
||||||
if [[ ${#selected_words[@]} -lt $words_count ]]; then
|
|
||||||
echo -e "${RED}Warning: Could only find ${#selected_words[@]} words of length $word_length.${NC}"
|
|
||||||
words_count=${#selected_words[@]}
|
|
||||||
if [[ $words_count -eq 0 ]]; then
|
|
||||||
echo -e "${RED}Error: No words of length $word_length found in $words_file${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Convert all words to uppercase
|
|
||||||
for i in "${!selected_words[@]}"; do
|
|
||||||
selected_words[i]=$(echo "${selected_words[i]}" | tr '[:lower:]' '[:upper:]')
|
|
||||||
done
|
|
||||||
|
|
||||||
echo -e "${CYAN}Here are ${words_count} random words. Remember them:${NC}"
|
|
||||||
|
|
||||||
# Display the words in a grid (4 columns)
|
|
||||||
for ((i = 0; i < words_count; i++)); do
|
|
||||||
printf "${BLUE}%-15s${NC}" "${selected_words[i]}"
|
|
||||||
if (((i + 1) % 4 == 0)); then
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Select a random word to scramble (already in uppercase)
|
|
||||||
target_index=$((RANDOM % words_count))
|
|
||||||
target_word="${selected_words[target_index]}"
|
|
||||||
|
|
||||||
# Scramble the word
|
|
||||||
scrambled_word=$(echo "$target_word" | fold -w1 | shuf | tr -d '\n')
|
|
||||||
|
|
||||||
# Ensure scrambled word is different from original
|
|
||||||
if [[ $scrambled_word == "$target_word" ]]; then
|
|
||||||
# Use simple reversal as fallback
|
|
||||||
scrambled_word=$(echo "$target_word" | rev)
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n${YELLOW}One of those words has been scrambled to:${NC} ${CYAN}$scrambled_word${NC}"
|
|
||||||
echo -e "${YELLOW}Unscramble the word to proceed with installation (you have 90 seconds):${NC}"
|
|
||||||
|
|
||||||
# Set up a background process to display the timer
|
|
||||||
(
|
|
||||||
start_time=$(date +%s)
|
|
||||||
while true; do
|
|
||||||
current_time=$(date +%s)
|
|
||||||
elapsed=$((current_time - start_time))
|
|
||||||
remaining=$((90 - elapsed))
|
|
||||||
|
|
||||||
if [[ $remaining -le 0 ]]; then
|
|
||||||
echo -ne "\r${YELLOW}Time remaining: 0 seconds${NC} "
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -ne "\r${YELLOW}Time remaining: ${remaining} seconds${NC} "
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
) &
|
|
||||||
display_pid=$!
|
|
||||||
|
|
||||||
# Read user input with timeout (90 seconds for VirtualBox)
|
|
||||||
read -t 90 -r user_input
|
|
||||||
read_status=$?
|
|
||||||
|
|
||||||
# Kill the timer display
|
|
||||||
kill "$display_pid" 2>/dev/null
|
|
||||||
wait "$display_pid" 2>/dev/null
|
|
||||||
echo # Add a newline after the timer
|
|
||||||
|
|
||||||
# Check if read timed out
|
|
||||||
if [[ $read_status -ne 0 ]]; then
|
|
||||||
echo -e "${RED}Time's up! Greylist challenge failed. The correct word was '$target_word'.${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Convert user input to uppercase and trim whitespaces
|
|
||||||
user_input=$(echo "$user_input" | tr '[:lower:]' '[:upper:]' | xargs)
|
|
||||||
|
|
||||||
if [[ $user_input == "$target_word" ]]; then
|
|
||||||
echo -e "${GREEN}Correct! Proceeding with installation...${NC}"
|
|
||||||
|
|
||||||
# Add sleep after successful challenge completion (15-35 seconds)
|
|
||||||
post_challenge_sleep=$((RANDOM % 20 + 15))
|
|
||||||
sleep "$post_challenge_sleep"
|
|
||||||
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo -e "${RED}Incorrect answer. Installation aborted. The correct word was '$target_word'.${NC}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for wrapper-specific commands
|
# Check for wrapper-specific commands
|
||||||
|
|||||||
@ -6,6 +6,11 @@
|
|||||||
|
|
||||||
set -e # Exit on any error
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Function to show usage
|
# Function to show usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
|
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
|
||||||
@ -35,13 +40,7 @@ check_sudo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Get the actual user (even when running with sudo)
|
# Get the actual user (even when running with sudo)
|
||||||
if [[ -n $SUDO_USER ]]; then
|
set_actual_user_vars
|
||||||
ACTUAL_USER="$SUDO_USER"
|
|
||||||
USER_HOME="/home/$SUDO_USER"
|
|
||||||
else
|
|
||||||
ACTUAL_USER="$USER"
|
|
||||||
USER_HOME="$HOME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Function to show current status
|
# Function to show current status
|
||||||
show_current_status() {
|
show_current_status() {
|
||||||
@ -603,6 +602,13 @@ test_setup() {
|
|||||||
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available"
|
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Display the shutdown schedule (used in multiple places)
|
||||||
|
print_shutdown_schedule() {
|
||||||
|
echo "Shutdown Schedule:"
|
||||||
|
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
|
||||||
|
echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)"
|
||||||
|
}
|
||||||
|
|
||||||
# Function to show final instructions
|
# Function to show final instructions
|
||||||
show_instructions() {
|
show_instructions() {
|
||||||
echo ""
|
echo ""
|
||||||
@ -618,9 +624,7 @@ show_instructions() {
|
|||||||
echo "✓ Monitor service installed (protects timer from being disabled)"
|
echo "✓ Monitor service installed (protects timer from being disabled)"
|
||||||
echo "✓ Watchdog timer installed (restarts monitor if stopped)"
|
echo "✓ Watchdog timer installed (restarts monitor if stopped)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Shutdown Schedule:"
|
print_shutdown_schedule
|
||||||
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
|
|
||||||
echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Management commands:"
|
echo "Management commands:"
|
||||||
echo " sudo day-specific-shutdown-manager.sh status - Check status"
|
echo " sudo day-specific-shutdown-manager.sh status - Check status"
|
||||||
@ -646,9 +650,7 @@ confirm_setup() {
|
|||||||
echo "==============================================="
|
echo "==============================================="
|
||||||
echo "This will set up your PC to automatically shutdown during specific time windows."
|
echo "This will set up your PC to automatically shutdown during specific time windows."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Shutdown Schedule:"
|
print_shutdown_schedule
|
||||||
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
|
|
||||||
echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Important considerations:"
|
echo "Important considerations:"
|
||||||
echo "- Any unsaved work will be lost during shutdown windows"
|
echo "- Any unsaved work will be lost during shutdown windows"
|
||||||
|
|||||||
@ -10,14 +10,13 @@ set -euo pipefail
|
|||||||
|
|
||||||
SCRIPT_NAME="$(basename "$0")"
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# ---------- User/paths ----------
|
# ---------- User/paths ----------
|
||||||
if [[ -n ${SUDO_USER:-} ]]; then
|
set_actual_user_vars
|
||||||
ACTUAL_USER="$SUDO_USER"
|
|
||||||
USER_HOME="/home/$SUDO_USER"
|
|
||||||
else
|
|
||||||
ACTUAL_USER="$USER"
|
|
||||||
USER_HOME="$HOME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
INSTALL_ROOT_DEFAULT="$USER_HOME/.local/share/unreal-mcp"
|
INSTALL_ROOT_DEFAULT="$USER_HOME/.local/share/unreal-mcp"
|
||||||
INSTALL_ROOT="$INSTALL_ROOT_DEFAULT"
|
INSTALL_ROOT="$INSTALL_ROOT_DEFAULT"
|
||||||
|
|||||||
@ -13,7 +13,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf"
|
CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf"
|
||||||
|
|
||||||
# Load configuration from gitignored config file if it exists
|
# Load configuration from gitignored config file if it exists
|
||||||
if [[ -f "$CONFIG_FILE" ]]; then
|
if [[ -f $CONFIG_FILE ]]; then
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
@ -93,7 +93,7 @@ generate_password() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto_generate_pi_password() {
|
auto_generate_pi_password() {
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
PI_PASSWORD=$(generate_password 16)
|
PI_PASSWORD=$(generate_password 16)
|
||||||
log_info "Auto-generated Pi password (will be saved to config file)"
|
log_info "Auto-generated Pi password (will be saved to config file)"
|
||||||
fi
|
fi
|
||||||
@ -150,7 +150,7 @@ discover_remote_laptop() {
|
|||||||
nmap -sn -T4 "$network" &>/dev/null || true
|
nmap -sn -T4 "$network" &>/dev/null || true
|
||||||
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
|
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
|
||||||
|
|
||||||
if [[ -z "$ssh_hosts" ]]; then
|
if [[ -z $ssh_hosts ]]; then
|
||||||
die "No SSH-enabled devices found on network"
|
die "No SSH-enabled devices found on network"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ discover_remote_laptop() {
|
|||||||
for u in "${common_users[@]}"; do
|
for u in "${common_users[@]}"; do
|
||||||
local is_dup=0
|
local is_dup=0
|
||||||
for existing in "${users[@]}"; do
|
for existing in "${users[@]}"; do
|
||||||
if [[ "$u" == "$existing" ]]; then
|
if [[ $u == "$existing" ]]; then
|
||||||
is_dup=1
|
is_dup=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@ -182,7 +182,7 @@ discover_remote_laptop() {
|
|||||||
for ip in $ssh_hosts; do
|
for ip in $ssh_hosts; do
|
||||||
idx=$((idx + 1))
|
idx=$((idx + 1))
|
||||||
|
|
||||||
if [[ "$ip" == "$gateway" ]]; then
|
if [[ $ip == "$gateway" ]]; then
|
||||||
log_info "[$idx/$host_count] Skipping $ip (gateway)"
|
log_info "[$idx/$host_count] Skipping $ip (gateway)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@ -198,13 +198,13 @@ discover_remote_laptop() {
|
|||||||
local has_sd
|
local has_sd
|
||||||
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true)
|
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -n "$has_sd" ]]; then
|
if [[ -n $has_sd ]]; then
|
||||||
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
|
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
|
||||||
found_laptop="$ip"
|
found_laptop="$ip"
|
||||||
break 2
|
break 2
|
||||||
else
|
else
|
||||||
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
|
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
|
||||||
if [[ -z "$found_laptop" ]]; then
|
if [[ -z $found_laptop ]]; then
|
||||||
found_laptop="$ip"
|
found_laptop="$ip"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -213,19 +213,19 @@ discover_remote_laptop() {
|
|||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$found_laptop" ]] || [[ -z "$found_user" ]]; then
|
if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then
|
||||||
log_warning "No device with passwordless SSH found using common usernames."
|
log_warning "No device with passwordless SSH found using common usernames."
|
||||||
|
|
||||||
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
|
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
|
||||||
|
|
||||||
if [[ -z "$found_laptop" ]]; then
|
if [[ -z $found_laptop ]]; then
|
||||||
die "Could not find any suitable SSH-enabled device"
|
die "Could not find any suitable SSH-enabled device"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Found SSH host at $found_laptop but need credentials."
|
log_info "Found SSH host at $found_laptop but need credentials."
|
||||||
read -r -p "Enter username for $found_laptop: " found_user
|
read -r -p "Enter username for $found_laptop: " found_user
|
||||||
|
|
||||||
if [[ -z "$found_user" ]]; then
|
if [[ -z $found_user ]]; then
|
||||||
die "No username provided"
|
die "No username provided"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -279,16 +279,16 @@ download_raspberry_pi_os() {
|
|||||||
|
|
||||||
mkdir -p "$download_dir"
|
mkdir -p "$download_dir"
|
||||||
|
|
||||||
if [[ -f "$extracted_image" ]]; then
|
if [[ -f $extracted_image ]]; then
|
||||||
log_info "Using existing image at $extracted_image"
|
log_info "Using existing image at $extracted_image"
|
||||||
echo "$extracted_image"
|
echo "$extracted_image"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$image_file" ]]; then
|
if [[ -f $image_file ]]; then
|
||||||
local actual_size
|
local actual_size
|
||||||
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
||||||
if [[ "$actual_size" -lt "$expected_size" ]]; then
|
if [[ $actual_size -lt $expected_size ]]; then
|
||||||
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
|
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
|
||||||
rm -f "$image_file"
|
rm -f "$image_file"
|
||||||
else
|
else
|
||||||
@ -296,7 +296,7 @@ download_raspberry_pi_os() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$image_file" ]]; then
|
if [[ ! -f $image_file ]]; then
|
||||||
log_info "Downloading Raspberry Pi OS Lite (64-bit)..."
|
log_info "Downloading Raspberry Pi OS Lite (64-bit)..."
|
||||||
log_info "This may take a while depending on your internet connection..."
|
log_info "This may take a while depending on your internet connection..."
|
||||||
|
|
||||||
@ -312,7 +312,7 @@ download_raspberry_pi_os() {
|
|||||||
|
|
||||||
local actual_size
|
local actual_size
|
||||||
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
||||||
if [[ "$actual_size" -lt "$expected_size" ]]; then
|
if [[ $actual_size -lt $expected_size ]]; then
|
||||||
die "Download incomplete: got $actual_size bytes, expected $expected_size"
|
die "Download incomplete: got $actual_size bytes, expected $expected_size"
|
||||||
fi
|
fi
|
||||||
log_success "Download complete: $actual_size bytes"
|
log_success "Download complete: $actual_size bytes"
|
||||||
@ -321,7 +321,7 @@ download_raspberry_pi_os() {
|
|||||||
log_info "Extracting image..."
|
log_info "Extracting image..."
|
||||||
xz -dk "$image_file"
|
xz -dk "$image_file"
|
||||||
|
|
||||||
if [[ ! -f "$extracted_image" ]]; then
|
if [[ ! -f $extracted_image ]]; then
|
||||||
die "Failed to extract image"
|
die "Failed to extract image"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -342,7 +342,7 @@ phase_flash_local() {
|
|||||||
local devices
|
local devices
|
||||||
devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}')
|
devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}')
|
||||||
|
|
||||||
if [[ -z "$devices" ]]; then
|
if [[ -z $devices ]]; then
|
||||||
log_warning "No removable devices detected automatically."
|
log_warning "No removable devices detected automatically."
|
||||||
lsblk -d -o NAME,SIZE,TYPE,RM,TRAN
|
lsblk -d -o NAME,SIZE,TYPE,RM,TRAN
|
||||||
read -r -p "Enter the SD card device path (e.g., /dev/sdb): " SD_CARD_DEVICE
|
read -r -p "Enter the SD card device path (e.g., /dev/sdb): " SD_CARD_DEVICE
|
||||||
@ -352,13 +352,13 @@ phase_flash_local() {
|
|||||||
read -r -p "Enter the SD card device path from above (e.g., /dev/sdb): " SD_CARD_DEVICE
|
read -r -p "Enter the SD card device path from above (e.g., /dev/sdb): " SD_CARD_DEVICE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -b "$SD_CARD_DEVICE" ]]; then
|
if [[ ! -b $SD_CARD_DEVICE ]]; then
|
||||||
die "Device $SD_CARD_DEVICE does not exist or is not a block device"
|
die "Device $SD_CARD_DEVICE does not exist or is not a block device"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local root_device
|
local root_device
|
||||||
root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//')
|
root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//')
|
||||||
if [[ "$SD_CARD_DEVICE" == "$root_device" ]]; then
|
if [[ $SD_CARD_DEVICE == "$root_device" ]]; then
|
||||||
die "Cannot flash to the system drive!"
|
die "Cannot flash to the system drive!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -375,7 +375,7 @@ phase_flash_local() {
|
|||||||
log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE"
|
log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE"
|
||||||
read -r -p "Are you sure you want to continue? (yes/no): " confirm
|
read -r -p "Are you sure you want to continue? (yes/no): " confirm
|
||||||
|
|
||||||
if [[ "$confirm" != "yes" ]]; then
|
if [[ $confirm != "yes" ]]; then
|
||||||
die "Aborted by user"
|
die "Aborted by user"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -423,7 +423,7 @@ phase_flash_local() {
|
|||||||
root_partition="${SD_CARD_DEVICE}p2"
|
root_partition="${SD_CARD_DEVICE}p2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$root_partition" ]]; then
|
if [[ -n $root_partition ]]; then
|
||||||
local root_mount="/tmp/rpi-root"
|
local root_mount="/tmp/rpi-root"
|
||||||
mkdir -p "$root_mount"
|
mkdir -p "$root_mount"
|
||||||
mount "$root_partition" "$root_mount"
|
mount "$root_partition" "$root_mount"
|
||||||
@ -475,7 +475,7 @@ phase_flash_remote() {
|
|||||||
local sd_device
|
local sd_device
|
||||||
sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true)
|
sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -z "$sd_device" ]]; then
|
if [[ -z $sd_device ]]; then
|
||||||
die "No SD card detected on remote laptop. Please insert an SD card and try again."
|
die "No SD card detected on remote laptop. Please insert an SD card and try again."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -530,7 +530,7 @@ phase_execute_remote() {
|
|||||||
|
|
||||||
log_info "=== Executing Flash on Remote Laptop ==="
|
log_info "=== Executing Flash on Remote Laptop ==="
|
||||||
|
|
||||||
if [[ -z "$SD_CARD_DEVICE" ]]; then
|
if [[ -z $SD_CARD_DEVICE ]]; then
|
||||||
die "SD_CARD_DEVICE not set"
|
die "SD_CARD_DEVICE not set"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -570,7 +570,7 @@ phase_execute_remote() {
|
|||||||
touch "$boot_mount/ssh"
|
touch "$boot_mount/ssh"
|
||||||
log_success "SSH enabled"
|
log_success "SSH enabled"
|
||||||
|
|
||||||
if [[ -n "$encrypted_password" ]]; then
|
if [[ -n $encrypted_password ]]; then
|
||||||
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
|
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
|
||||||
log_success "User '$PI_USER' configured"
|
log_success "User '$PI_USER' configured"
|
||||||
fi
|
fi
|
||||||
@ -582,7 +582,7 @@ phase_execute_remote() {
|
|||||||
root_partition="${SD_CARD_DEVICE}p2"
|
root_partition="${SD_CARD_DEVICE}p2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$root_partition" ]]; then
|
if [[ -n $root_partition ]]; then
|
||||||
local root_mount="/tmp/rpi-root"
|
local root_mount="/tmp/rpi-root"
|
||||||
mkdir -p "$root_mount"
|
mkdir -p "$root_mount"
|
||||||
mount "$root_partition" "$root_mount"
|
mount "$root_partition" "$root_mount"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf"
|
CONFIG_FILE="${SCRIPT_DIR}/.raspberry_pi.conf"
|
||||||
|
|
||||||
# Load configuration from gitignored config file if it exists
|
# Load configuration from gitignored config file if it exists
|
||||||
if [[ -f "$CONFIG_FILE" ]]; then
|
if [[ -f $CONFIG_FILE ]]; then
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
@ -102,7 +102,7 @@ generate_password() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto_generate_nextcloud_password() {
|
auto_generate_nextcloud_password() {
|
||||||
if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then
|
if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then
|
||||||
NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20)
|
NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20)
|
||||||
log_info "Auto-generated Nextcloud admin password (will be saved to config file)"
|
log_info "Auto-generated Nextcloud admin password (will be saved to config file)"
|
||||||
fi
|
fi
|
||||||
@ -180,11 +180,11 @@ discover_raspberry_pi() {
|
|||||||
|
|
||||||
# Try resolving hostname directly
|
# Try resolving hostname directly
|
||||||
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
|
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
|
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$pi_ip" ]]; then
|
if [[ -n $pi_ip ]]; then
|
||||||
log_success "Found Pi by hostname: $pi_ip"
|
log_success "Found Pi by hostname: $pi_ip"
|
||||||
echo "$pi_ip"
|
echo "$pi_ip"
|
||||||
return
|
return
|
||||||
@ -196,7 +196,7 @@ discover_raspberry_pi() {
|
|||||||
local ssh_hosts
|
local ssh_hosts
|
||||||
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) || true
|
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u) || true
|
||||||
|
|
||||||
if [[ -z "$ssh_hosts" ]]; then
|
if [[ -z $ssh_hosts ]]; then
|
||||||
die "No SSH-enabled devices found. Is the Pi connected and booted?"
|
die "No SSH-enabled devices found. Is the Pi connected and booted?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -599,7 +599,7 @@ phase_fix_issues() {
|
|||||||
|
|
||||||
# Generate server certificate signed by our CA
|
# Generate server certificate signed by our CA
|
||||||
local regenerate="${1:-}"
|
local regenerate="${1:-}"
|
||||||
if [[ ! -f "$ssl_dir/server.crt" ]] || [[ "$regenerate" == "--regenerate" ]]; then
|
if [[ ! -f "$ssl_dir/server.crt" ]] || [[ $regenerate == "--regenerate" ]]; then
|
||||||
log_info "Generating server certificate signed by CA..."
|
log_info "Generating server certificate signed by CA..."
|
||||||
|
|
||||||
# Generate server private key
|
# Generate server private key
|
||||||
@ -718,7 +718,7 @@ EOF
|
|||||||
|
|
||||||
# Enable SVG in ImageMagick policy
|
# Enable SVG in ImageMagick policy
|
||||||
local policy_file="/etc/ImageMagick-6/policy.xml"
|
local policy_file="/etc/ImageMagick-6/policy.xml"
|
||||||
if [[ -f "$policy_file" ]]; then
|
if [[ -f $policy_file ]]; then
|
||||||
# Remove SVG restrictions if present
|
# Remove SVG restrictions if present
|
||||||
sed -i 's/<policy domain="coder" rights="none" pattern="SVG" \/>/<policy domain="coder" rights="read|write" pattern="SVG" \/>/' "$policy_file"
|
sed -i 's/<policy domain="coder" rights="none" pattern="SVG" \/>/<policy domain="coder" rights="read|write" pattern="SVG" \/>/' "$policy_file"
|
||||||
# If no SVG policy exists, add one allowing it
|
# If no SVG policy exists, add one allowing it
|
||||||
@ -777,7 +777,7 @@ phase_setup_ssl() {
|
|||||||
log_info "=== Setting up Let's Encrypt SSL with DuckDNS ==="
|
log_info "=== Setting up Let's Encrypt SSL with DuckDNS ==="
|
||||||
|
|
||||||
# Check if DuckDNS is configured
|
# Check if DuckDNS is configured
|
||||||
if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then
|
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then
|
||||||
echo
|
echo
|
||||||
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
||||||
log_info "1. Go to https://www.duckdns.org/ and sign in with Google/GitHub/etc."
|
log_info "1. Go to https://www.duckdns.org/ and sign in with Google/GitHub/etc."
|
||||||
@ -789,7 +789,7 @@ phase_setup_ssl() {
|
|||||||
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
|
read -r -p "Enter your DuckDNS token: " DUCKDNS_TOKEN
|
||||||
read -r -p "Enter your email (for Let's Encrypt notifications): " LETSENCRYPT_EMAIL
|
read -r -p "Enter your email (for Let's Encrypt notifications): " LETSENCRYPT_EMAIL
|
||||||
|
|
||||||
if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]] || [[ -z "$LETSENCRYPT_EMAIL" ]]; then
|
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]] || [[ -z $LETSENCRYPT_EMAIL ]]; then
|
||||||
die "All fields are required"
|
die "All fields are required"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -817,7 +817,7 @@ phase_setup_ssl() {
|
|||||||
echo
|
echo
|
||||||
read -r -p "Have you set up port forwarding? (yes/no): " port_forward_done
|
read -r -p "Have you set up port forwarding? (yes/no): " port_forward_done
|
||||||
|
|
||||||
if [[ "$port_forward_done" != "yes" ]]; then
|
if [[ $port_forward_done != "yes" ]]; then
|
||||||
log_info "Please set up port forwarding and run this command again."
|
log_info "Please set up port forwarding and run this command again."
|
||||||
log_info "Without port forwarding, Let's Encrypt cannot verify your domain."
|
log_info "Without port forwarding, Let's Encrypt cannot verify your domain."
|
||||||
exit 0
|
exit 0
|
||||||
@ -829,7 +829,7 @@ phase_setup_ssl() {
|
|||||||
# When ip= is empty, DuckDNS auto-detects the public IP
|
# When ip= is empty, DuckDNS auto-detects the public IP
|
||||||
duckdns_response=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
|
duckdns_response=$(curl -s "https://www.duckdns.org/update?domains=${DUCKDNS_DOMAIN}&token=${DUCKDNS_TOKEN}&ip=")
|
||||||
|
|
||||||
if [[ "$duckdns_response" != "OK" ]]; then
|
if [[ $duckdns_response != "OK" ]]; then
|
||||||
die "Failed to update DuckDNS: $duckdns_response"
|
die "Failed to update DuckDNS: $duckdns_response"
|
||||||
fi
|
fi
|
||||||
log_success "DuckDNS updated to public IP"
|
log_success "DuckDNS updated to public IP"
|
||||||
@ -855,14 +855,14 @@ DUCKEOF
|
|||||||
log_info "Waiting for DNS propagation (this may take a minute)..."
|
log_info "Waiting for DNS propagation (this may take a minute)..."
|
||||||
local dns_ip=""
|
local dns_ip=""
|
||||||
local attempts=0
|
local attempts=0
|
||||||
while [[ "$dns_ip" != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do
|
while [[ $dns_ip != "$public_ip" ]] && [[ $attempts -lt 12 ]]; do
|
||||||
sleep 5
|
sleep 5
|
||||||
dns_ip=$(dig +short "$full_domain" 2>/dev/null | tail -1) || true
|
dns_ip=$(dig +short "$full_domain" 2>/dev/null | tail -1) || true
|
||||||
attempts=$((attempts + 1))
|
attempts=$((attempts + 1))
|
||||||
log_info " DNS lookup: $dns_ip (expecting $public_ip, attempt $attempts/12)"
|
log_info " DNS lookup: $dns_ip (expecting $public_ip, attempt $attempts/12)"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$dns_ip" != "$public_ip" ]]; then
|
if [[ $dns_ip != "$public_ip" ]]; then
|
||||||
log_warning "DNS may not have propagated yet. Continuing anyway..."
|
log_warning "DNS may not have propagated yet. Continuing anyway..."
|
||||||
else
|
else
|
||||||
log_success "DNS verified: $full_domain -> $public_ip"
|
log_success "DNS verified: $full_domain -> $public_ip"
|
||||||
@ -961,19 +961,19 @@ EOF
|
|||||||
phase_setup_ssl_remote() {
|
phase_setup_ssl_remote() {
|
||||||
log_info "=== Setting up Let's Encrypt SSL via SSH ==="
|
log_info "=== Setting up Let's Encrypt SSL via SSH ==="
|
||||||
|
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
die "PI_PASSWORD not set. Run install-remote first."
|
die "PI_PASSWORD not set. Run install-remote first."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local pi_ip
|
local pi_ip
|
||||||
pi_ip=$(discover_raspberry_pi)
|
pi_ip=$(discover_raspberry_pi)
|
||||||
|
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
die "Failed to discover Raspberry Pi"
|
die "Failed to discover Raspberry Pi"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get DuckDNS credentials if not set
|
# Get DuckDNS credentials if not set
|
||||||
if [[ -z "$DUCKDNS_DOMAIN" ]] || [[ -z "$DUCKDNS_TOKEN" ]]; then
|
if [[ -z $DUCKDNS_DOMAIN ]] || [[ -z $DUCKDNS_TOKEN ]]; then
|
||||||
echo
|
echo
|
||||||
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
log_info "To get auto-trusted HTTPS, you need a free DuckDNS domain."
|
||||||
log_info "1. Go to https://www.duckdns.org/ and sign in"
|
log_info "1. Go to https://www.duckdns.org/ and sign in"
|
||||||
@ -1012,14 +1012,14 @@ phase_setup_ssl_remote() {
|
|||||||
phase_install_remote() {
|
phase_install_remote() {
|
||||||
log_info "=== Installing Nextcloud via SSH ==="
|
log_info "=== Installing Nextcloud via SSH ==="
|
||||||
|
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
die "PI_PASSWORD not set. Did you run flash script first?"
|
die "PI_PASSWORD not set. Did you run flash script first?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local pi_ip
|
local pi_ip
|
||||||
pi_ip=$(discover_raspberry_pi)
|
pi_ip=$(discover_raspberry_pi)
|
||||||
|
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
die "Failed to discover Raspberry Pi"
|
die "Failed to discover Raspberry Pi"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1070,14 +1070,14 @@ phase_install_remote() {
|
|||||||
phase_install_ca() {
|
phase_install_ca() {
|
||||||
log_info "=== Installing Nextcloud CA Certificate ==="
|
log_info "=== Installing Nextcloud CA Certificate ==="
|
||||||
|
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
die "PI_PASSWORD not set. Run this after running install-remote or flash."
|
die "PI_PASSWORD not set. Run this after running install-remote or flash."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local pi_ip
|
local pi_ip
|
||||||
pi_ip=$(discover_raspberry_pi)
|
pi_ip=$(discover_raspberry_pi)
|
||||||
|
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
die "Failed to discover Raspberry Pi"
|
die "Failed to discover Raspberry Pi"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1089,7 +1089,7 @@ phase_install_ca() {
|
|||||||
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no \
|
sshpass -p "$PI_PASSWORD" ssh -o StrictHostKeyChecking=no \
|
||||||
"${PI_USER}@${pi_ip}" "echo '$PI_PASSWORD' | sudo -S cat /etc/ssl/nextcloud/ca.crt" >"$ca_file" 2>/dev/null
|
"${PI_USER}@${pi_ip}" "echo '$PI_PASSWORD' | sudo -S cat /etc/ssl/nextcloud/ca.crt" >"$ca_file" 2>/dev/null
|
||||||
|
|
||||||
if [[ ! -f "$ca_file" ]] || [[ ! -s "$ca_file" ]]; then
|
if [[ ! -f $ca_file ]] || [[ ! -s $ca_file ]]; then
|
||||||
die "Failed to download CA certificate"
|
die "Failed to download CA certificate"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1146,7 +1146,7 @@ phase_install_ca() {
|
|||||||
if [[ -d ~/.mozilla/firefox ]]; then
|
if [[ -d ~/.mozilla/firefox ]]; then
|
||||||
local installed=0
|
local installed=0
|
||||||
for profile_dir in ~/.mozilla/firefox/*.default* ~/.mozilla/firefox/*.esr*; do
|
for profile_dir in ~/.mozilla/firefox/*.default* ~/.mozilla/firefox/*.esr*; do
|
||||||
if [[ -d "$profile_dir" ]]; then
|
if [[ -d $profile_dir ]]; then
|
||||||
if ! certutil -d sql:"$profile_dir" -L 2>/dev/null | grep -q "Nextcloud"; then
|
if ! certutil -d sql:"$profile_dir" -L 2>/dev/null | grep -q "Nextcloud"; then
|
||||||
certutil -d sql:"$profile_dir" -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null &&
|
certutil -d sql:"$profile_dir" -A -n "Nextcloud Home CA" -t "CT,C,C" -i "$ca_file" 2>/dev/null &&
|
||||||
installed=1
|
installed=1
|
||||||
|
|||||||
@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
set -e # Exit on any error
|
set -e # Exit on any error
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Function to check and request sudo privileges for package installation
|
# Function to check and request sudo privileges for package installation
|
||||||
check_sudo() {
|
check_sudo() {
|
||||||
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
|
if [[ $EUID -ne 0 ]] && [[ $1 == "install" ]]; then
|
||||||
@ -14,20 +19,13 @@ check_sudo() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Get the actual user (even when running with sudo)
|
||||||
|
set_actual_user_vars
|
||||||
|
|
||||||
echo "ActivityWatch Setup for Arch Linux + i3"
|
echo "ActivityWatch Setup for Arch Linux + i3"
|
||||||
echo "======================================="
|
echo "======================================="
|
||||||
echo "Current Date: $(date)"
|
echo "Current Date: $(date)"
|
||||||
echo "User: ${SUDO_USER:-$USER}"
|
echo "User: $ACTUAL_USER"
|
||||||
|
|
||||||
# Get the actual user (even when running with sudo)
|
|
||||||
if [[ -n $SUDO_USER ]]; then
|
|
||||||
ACTUAL_USER="$SUDO_USER"
|
|
||||||
USER_HOME="/home/$SUDO_USER"
|
|
||||||
else
|
|
||||||
ACTUAL_USER="$USER"
|
|
||||||
USER_HOME="$HOME"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Target user: $ACTUAL_USER"
|
echo "Target user: $ACTUAL_USER"
|
||||||
echo "User home: $USER_HOME"
|
echo "User home: $USER_HOME"
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
CONFIG_FILE="${SCRIPT_DIR}/.nextcloud_raspberry.conf"
|
CONFIG_FILE="${SCRIPT_DIR}/.nextcloud_raspberry.conf"
|
||||||
|
|
||||||
# Load configuration from gitignored config file if it exists
|
# Load configuration from gitignored config file if it exists
|
||||||
if [[ -f "$CONFIG_FILE" ]]; then
|
if [[ -f $CONFIG_FILE ]]; then
|
||||||
# shellcheck source=/dev/null
|
# shellcheck source=/dev/null
|
||||||
source "$CONFIG_FILE"
|
source "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
@ -108,14 +108,14 @@ generate_password() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto_generate_pi_password() {
|
auto_generate_pi_password() {
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
PI_PASSWORD=$(generate_password 16)
|
PI_PASSWORD=$(generate_password 16)
|
||||||
log_info "Auto-generated Pi password (will be saved to config file)"
|
log_info "Auto-generated Pi password (will be saved to config file)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
auto_generate_nextcloud_password() {
|
auto_generate_nextcloud_password() {
|
||||||
if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then
|
if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then
|
||||||
NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20)
|
NEXTCLOUD_ADMIN_PASSWORD=$(generate_password 20)
|
||||||
log_info "Auto-generated Nextcloud admin password (will be saved to config file)"
|
log_info "Auto-generated Nextcloud admin password (will be saved to config file)"
|
||||||
fi
|
fi
|
||||||
@ -133,8 +133,8 @@ prompt_password() {
|
|||||||
read -r -s -p "Confirm password: " password_confirm
|
read -r -s -p "Confirm password: " password_confirm
|
||||||
echo
|
echo
|
||||||
|
|
||||||
if [[ "$password" == "$password_confirm" ]]; then
|
if [[ $password == "$password_confirm" ]]; then
|
||||||
if [[ -z "$password" ]]; then
|
if [[ -z $password ]]; then
|
||||||
log_warning "Password cannot be empty. Please try again."
|
log_warning "Password cannot be empty. Please try again."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@ -157,7 +157,7 @@ detect_sd_card() {
|
|||||||
local devices
|
local devices
|
||||||
devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}')
|
devices=$(lsblk -d -o NAME,SIZE,TYPE,RM,TRAN | grep -E "disk.*1.*usb|disk.*1.*mmc" | awk '{print "/dev/"$1" ("$2")"}')
|
||||||
|
|
||||||
if [[ -z "$devices" ]]; then
|
if [[ -z $devices ]]; then
|
||||||
log_warning "No removable devices detected automatically."
|
log_warning "No removable devices detected automatically."
|
||||||
log_info "Available block devices:"
|
log_info "Available block devices:"
|
||||||
lsblk -d -o NAME,SIZE,TYPE,RM,TRAN
|
lsblk -d -o NAME,SIZE,TYPE,RM,TRAN
|
||||||
@ -171,14 +171,14 @@ detect_sd_card() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate device exists
|
# Validate device exists
|
||||||
if [[ ! -b "$SD_CARD_DEVICE" ]]; then
|
if [[ ! -b $SD_CARD_DEVICE ]]; then
|
||||||
die "Device $SD_CARD_DEVICE does not exist or is not a block device"
|
die "Device $SD_CARD_DEVICE does not exist or is not a block device"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Safety check - don't flash system drive
|
# Safety check - don't flash system drive
|
||||||
local root_device
|
local root_device
|
||||||
root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//')
|
root_device=$(findmnt -n -o SOURCE / | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//')
|
||||||
if [[ "$SD_CARD_DEVICE" == "$root_device" ]]; then
|
if [[ $SD_CARD_DEVICE == "$root_device" ]]; then
|
||||||
die "Cannot flash to the system drive!"
|
die "Cannot flash to the system drive!"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -192,17 +192,17 @@ download_raspberry_pi_os() {
|
|||||||
|
|
||||||
mkdir -p "$download_dir"
|
mkdir -p "$download_dir"
|
||||||
|
|
||||||
if [[ -f "$extracted_image" ]]; then
|
if [[ -f $extracted_image ]]; then
|
||||||
log_info "Using existing image at $extracted_image"
|
log_info "Using existing image at $extracted_image"
|
||||||
echo "$extracted_image"
|
echo "$extracted_image"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if download exists and is complete
|
# Check if download exists and is complete
|
||||||
if [[ -f "$image_file" ]]; then
|
if [[ -f $image_file ]]; then
|
||||||
local actual_size
|
local actual_size
|
||||||
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
||||||
if [[ "$actual_size" -lt "$expected_size" ]]; then
|
if [[ $actual_size -lt $expected_size ]]; then
|
||||||
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
|
log_warning "Incomplete download detected ($actual_size < $expected_size bytes), re-downloading..."
|
||||||
rm -f "$image_file"
|
rm -f "$image_file"
|
||||||
else
|
else
|
||||||
@ -210,7 +210,7 @@ download_raspberry_pi_os() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$image_file" ]]; then
|
if [[ ! -f $image_file ]]; then
|
||||||
log_info "Downloading Raspberry Pi OS Lite (64-bit)..."
|
log_info "Downloading Raspberry Pi OS Lite (64-bit)..."
|
||||||
log_info "This may take a while depending on your internet connection..."
|
log_info "This may take a while depending on your internet connection..."
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ download_raspberry_pi_os() {
|
|||||||
# Verify download size
|
# Verify download size
|
||||||
local actual_size
|
local actual_size
|
||||||
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
actual_size=$(stat -c%s "$image_file" 2>/dev/null || stat -f%z "$image_file" 2>/dev/null || echo 0)
|
||||||
if [[ "$actual_size" -lt "$expected_size" ]]; then
|
if [[ $actual_size -lt $expected_size ]]; then
|
||||||
die "Download incomplete: got $actual_size bytes, expected $expected_size"
|
die "Download incomplete: got $actual_size bytes, expected $expected_size"
|
||||||
fi
|
fi
|
||||||
log_success "Download complete: $actual_size bytes"
|
log_success "Download complete: $actual_size bytes"
|
||||||
@ -238,7 +238,7 @@ download_raspberry_pi_os() {
|
|||||||
log_info "Extracting image..."
|
log_info "Extracting image..."
|
||||||
xz -dk "$image_file"
|
xz -dk "$image_file"
|
||||||
|
|
||||||
if [[ ! -f "$extracted_image" ]]; then
|
if [[ ! -f $extracted_image ]]; then
|
||||||
die "Failed to extract image"
|
die "Failed to extract image"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ flash_sd_card() {
|
|||||||
log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE"
|
log_warning "This will ERASE ALL DATA on $SD_CARD_DEVICE"
|
||||||
read -r -p "Are you sure you want to continue? (yes/no): " confirm
|
read -r -p "Are you sure you want to continue? (yes/no): " confirm
|
||||||
|
|
||||||
if [[ "$confirm" != "yes" ]]; then
|
if [[ $confirm != "yes" ]]; then
|
||||||
die "Aborted by user"
|
die "Aborted by user"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ configure_headless_boot() {
|
|||||||
|
|
||||||
# Configure WiFi (optional)
|
# Configure WiFi (optional)
|
||||||
read -r -p "Do you want to configure WiFi? (y/n): " configure_wifi
|
read -r -p "Do you want to configure WiFi? (y/n): " configure_wifi
|
||||||
if [[ "$configure_wifi" == "y" ]]; then
|
if [[ $configure_wifi == "y" ]]; then
|
||||||
read -r -p "WiFi SSID: " wifi_ssid
|
read -r -p "WiFi SSID: " wifi_ssid
|
||||||
read -r -s -p "WiFi Password: " wifi_password
|
read -r -s -p "WiFi Password: " wifi_password
|
||||||
echo
|
echo
|
||||||
@ -320,7 +320,7 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create userconf.txt for first user (Raspberry Pi OS Bookworm+)
|
# Create userconf.txt for first user (Raspberry Pi OS Bookworm+)
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
prompt_password "Enter password for Pi user '$PI_USER'" PI_PASSWORD
|
prompt_password "Enter password for Pi user '$PI_USER'" PI_PASSWORD
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ EOF
|
|||||||
root_partition="${SD_CARD_DEVICE}p2"
|
root_partition="${SD_CARD_DEVICE}p2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$root_partition" ]]; then
|
if [[ -n $root_partition ]]; then
|
||||||
local root_mount="/tmp/rpi-root"
|
local root_mount="/tmp/rpi-root"
|
||||||
mkdir -p "$root_mount"
|
mkdir -p "$root_mount"
|
||||||
mount "$root_partition" "$root_mount"
|
mount "$root_partition" "$root_mount"
|
||||||
@ -474,7 +474,7 @@ discover_remote_laptop() {
|
|||||||
# Extract IPs from nmap output - grep for report lines then extract IP
|
# Extract IPs from nmap output - grep for report lines then extract IP
|
||||||
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
|
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | sort -u)
|
||||||
|
|
||||||
if [[ -z "$ssh_hosts" ]]; then
|
if [[ -z $ssh_hosts ]]; then
|
||||||
die "No SSH-enabled devices found on network"
|
die "No SSH-enabled devices found on network"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -489,7 +489,7 @@ discover_remote_laptop() {
|
|||||||
for u in "${common_users[@]}"; do
|
for u in "${common_users[@]}"; do
|
||||||
local is_dup=0
|
local is_dup=0
|
||||||
for existing in "${users[@]}"; do
|
for existing in "${users[@]}"; do
|
||||||
if [[ "$u" == "$existing" ]]; then
|
if [[ $u == "$existing" ]]; then
|
||||||
is_dup=1
|
is_dup=1
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@ -510,7 +510,7 @@ discover_remote_laptop() {
|
|||||||
idx=$((idx + 1))
|
idx=$((idx + 1))
|
||||||
|
|
||||||
# Skip gateway
|
# Skip gateway
|
||||||
if [[ "$ip" == "$gateway" ]]; then
|
if [[ $ip == "$gateway" ]]; then
|
||||||
log_info "[$idx/$host_count] Skipping $ip (gateway)"
|
log_info "[$idx/$host_count] Skipping $ip (gateway)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@ -528,13 +528,13 @@ discover_remote_laptop() {
|
|||||||
local has_sd
|
local has_sd
|
||||||
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true)
|
has_sd=$(ssh -o BatchMode=yes -o ConnectTimeout=2 "${try_user}@${ip}" "lsblk -d -o NAME,RM,TRAN 2>/dev/null | grep -E '1.*(usb|mmc)' | head -1" 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -n "$has_sd" ]]; then
|
if [[ -n $has_sd ]]; then
|
||||||
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
|
log_success "[$idx/$host_count] $ip - Found SD card: $has_sd"
|
||||||
found_laptop="$ip"
|
found_laptop="$ip"
|
||||||
break 2 # Break out of both loops
|
break 2 # Break out of both loops
|
||||||
else
|
else
|
||||||
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
|
log_warning "[$idx/$host_count] $ip - No SD card detected, saving as fallback..."
|
||||||
if [[ -z "$found_laptop" ]]; then
|
if [[ -z $found_laptop ]]; then
|
||||||
found_laptop="$ip"
|
found_laptop="$ip"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -542,26 +542,26 @@ discover_remote_laptop() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$found_user" ]]; then
|
if [[ -z $found_user ]]; then
|
||||||
log_info "[$idx/$host_count] $ip - No SSH key access with any common username"
|
log_info "[$idx/$host_count] $ip - No SSH key access with any common username"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# If no passwordless access found, prompt user for username
|
# If no passwordless access found, prompt user for username
|
||||||
if [[ -z "$found_laptop" ]] || [[ -z "$found_user" ]]; then
|
if [[ -z $found_laptop ]] || [[ -z $found_user ]]; then
|
||||||
log_warning "No device with passwordless SSH found using common usernames."
|
log_warning "No device with passwordless SSH found using common usernames."
|
||||||
|
|
||||||
# Pick first available SSH host
|
# Pick first available SSH host
|
||||||
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
|
found_laptop=$(echo "$ssh_hosts" | grep -vw "$gateway" | head -1)
|
||||||
|
|
||||||
if [[ -z "$found_laptop" ]]; then
|
if [[ -z $found_laptop ]]; then
|
||||||
die "Could not find any suitable SSH-enabled device"
|
die "Could not find any suitable SSH-enabled device"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Found SSH host at $found_laptop but need credentials."
|
log_info "Found SSH host at $found_laptop but need credentials."
|
||||||
read -r -p "Enter username for $found_laptop: " found_user
|
read -r -p "Enter username for $found_laptop: " found_user
|
||||||
|
|
||||||
if [[ -z "$found_user" ]]; then
|
if [[ -z $found_user ]]; then
|
||||||
die "No username provided"
|
die "No username provided"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -596,7 +596,7 @@ phase_flash_remote() {
|
|||||||
local sd_device
|
local sd_device
|
||||||
sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true)
|
sd_device=$(ssh "$remote" "lsblk -d -o NAME,RM,TRAN | grep -E '1.*(usb|mmc)' | awk '{print \"/dev/\"\$1}' | head -1" 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -z "$sd_device" ]]; then
|
if [[ -z $sd_device ]]; then
|
||||||
die "No SD card detected on remote laptop. Please insert an SD card and try again."
|
die "No SD card detected on remote laptop. Please insert an SD card and try again."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ phase_flash_remote_execute() {
|
|||||||
|
|
||||||
log_info "=== Executing Flash on Remote Laptop ==="
|
log_info "=== Executing Flash on Remote Laptop ==="
|
||||||
|
|
||||||
if [[ -z "$SD_CARD_DEVICE" ]]; then
|
if [[ -z $SD_CARD_DEVICE ]]; then
|
||||||
die "SD_CARD_DEVICE not set"
|
die "SD_CARD_DEVICE not set"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -703,7 +703,7 @@ phase_flash_remote_execute() {
|
|||||||
log_success "SSH enabled"
|
log_success "SSH enabled"
|
||||||
|
|
||||||
# Create userconf.txt for first user
|
# Create userconf.txt for first user
|
||||||
if [[ -n "$encrypted_password" ]]; then
|
if [[ -n $encrypted_password ]]; then
|
||||||
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
|
echo "${PI_USER}:${encrypted_password}" >"$boot_mount/userconf.txt"
|
||||||
log_success "User '$PI_USER' configured"
|
log_success "User '$PI_USER' configured"
|
||||||
fi
|
fi
|
||||||
@ -716,7 +716,7 @@ phase_flash_remote_execute() {
|
|||||||
root_partition="${SD_CARD_DEVICE}p2"
|
root_partition="${SD_CARD_DEVICE}p2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$root_partition" ]]; then
|
if [[ -n $root_partition ]]; then
|
||||||
local root_mount="/tmp/rpi-root"
|
local root_mount="/tmp/rpi-root"
|
||||||
mkdir -p "$root_mount"
|
mkdir -p "$root_mount"
|
||||||
mount "$root_partition" "$root_mount"
|
mount "$root_partition" "$root_mount"
|
||||||
@ -951,7 +951,7 @@ download_nextcloud() {
|
|||||||
local download_dir="/tmp"
|
local download_dir="/tmp"
|
||||||
local nc_zip="$download_dir/nextcloud.zip"
|
local nc_zip="$download_dir/nextcloud.zip"
|
||||||
|
|
||||||
if [[ -f "$nc_zip" ]]; then
|
if [[ -f $nc_zip ]]; then
|
||||||
log_info "Nextcloud archive already downloaded"
|
log_info "Nextcloud archive already downloaded"
|
||||||
else
|
else
|
||||||
wget -O "$nc_zip" "$nc_url"
|
wget -O "$nc_zip" "$nc_url"
|
||||||
@ -1069,7 +1069,7 @@ install_nextcloud() {
|
|||||||
local db_password
|
local db_password
|
||||||
db_password=$(cat /root/.nextcloud_db_password)
|
db_password=$(cat /root/.nextcloud_db_password)
|
||||||
|
|
||||||
if [[ -z "$NEXTCLOUD_ADMIN_PASSWORD" ]]; then
|
if [[ -z $NEXTCLOUD_ADMIN_PASSWORD ]]; then
|
||||||
prompt_password "Enter Nextcloud admin password" NEXTCLOUD_ADMIN_PASSWORD
|
prompt_password "Enter Nextcloud admin password" NEXTCLOUD_ADMIN_PASSWORD
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1206,11 +1206,11 @@ discover_raspberry_pi() {
|
|||||||
|
|
||||||
# Try resolving hostname directly
|
# Try resolving hostname directly
|
||||||
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
|
pi_ip=$(getent hosts "$PI_HOSTNAME" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
|
pi_ip=$(getent hosts "${PI_HOSTNAME}.local" 2>/dev/null | awk '{print $1}' | head -1) || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$pi_ip" ]]; then
|
if [[ -n $pi_ip ]]; then
|
||||||
log_success "Found Pi by hostname: $pi_ip"
|
log_success "Found Pi by hostname: $pi_ip"
|
||||||
echo "$pi_ip"
|
echo "$pi_ip"
|
||||||
return
|
return
|
||||||
@ -1224,7 +1224,7 @@ discover_raspberry_pi() {
|
|||||||
local ssh_hosts
|
local ssh_hosts
|
||||||
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | grep -vw "$REMOTE_LAPTOP_IP" 2>/dev/null | sort -u) || true
|
ssh_hosts=$(nmap -p 22 --open -sT -T4 "$network" 2>/dev/null | grep "Nmap scan report" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | grep -vw "$my_ip" | grep -vw "$REMOTE_LAPTOP_IP" 2>/dev/null | sort -u) || true
|
||||||
|
|
||||||
if [[ -z "$ssh_hosts" ]]; then
|
if [[ -z $ssh_hosts ]]; then
|
||||||
die "No new SSH-enabled devices found. Is the Pi connected and booted?"
|
die "No new SSH-enabled devices found. Is the Pi connected and booted?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1259,14 +1259,14 @@ phase_all_remote() {
|
|||||||
local pi_ip
|
local pi_ip
|
||||||
pi_ip=$(discover_raspberry_pi)
|
pi_ip=$(discover_raspberry_pi)
|
||||||
|
|
||||||
if [[ -z "$pi_ip" ]]; then
|
if [[ -z $pi_ip ]]; then
|
||||||
die "Failed to discover Raspberry Pi"
|
die "Failed to discover Raspberry Pi"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Using Raspberry Pi at: $pi_ip"
|
log_info "Using Raspberry Pi at: $pi_ip"
|
||||||
|
|
||||||
# PI_PASSWORD should already be set from config file
|
# PI_PASSWORD should already be set from config file
|
||||||
if [[ -z "$PI_PASSWORD" ]]; then
|
if [[ -z $PI_PASSWORD ]]; then
|
||||||
die "PI_PASSWORD not set. Did you run flash-remote first?"
|
die "PI_PASSWORD not set. Did you run flash-remote first?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -2,35 +2,22 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source common library
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
on_error() {
|
on_error() {
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
local line_number=$1
|
local line_number=$1
|
||||||
printf '\033[1;31m[ERROR]\033[0m Unexpected failure at line %s (exit code %s).\n' "${line_number}" "${exit_code}" >&2
|
log_error "Unexpected failure at line ${line_number} (exit code ${exit_code})."
|
||||||
}
|
}
|
||||||
trap 'on_error ${LINENO}' ERR
|
trap 'on_error ${LINENO}' ERR
|
||||||
|
|
||||||
log_info() {
|
|
||||||
printf '\033[1;34m[INFO]\033[0m %s\n' "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warn() {
|
|
||||||
printf '\033[1;33m[WARN]\033[0m %s\n' "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
require_root() {
|
|
||||||
if [[ ${EUID} -ne 0 ]]; then
|
|
||||||
log_error "This script must be run as root (try again with sudo)."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
require_pacman() {
|
require_pacman() {
|
||||||
if ! command -v pacman > /dev/null 2>&1; then
|
if ! has_cmd pacman; then
|
||||||
log_error "pacman not found. This script is intended for Arch Linux systems."
|
log_error "pacman not found. This script is intended for Arch Linux systems."
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,16 +17,7 @@ shift "$COMMON_ARGS_SHIFT"
|
|||||||
# Check for sudo privileges
|
# Check for sudo privileges
|
||||||
require_root "$@"
|
require_root "$@"
|
||||||
|
|
||||||
echo "NVIDIA Comprehensive Troubleshooter & GSP Disabler"
|
print_setup_header "NVIDIA Comprehensive Troubleshooter & GSP Disabler"
|
||||||
echo "=================================================="
|
|
||||||
echo "Current Date: $(date)"
|
|
||||||
echo "User: $USER"
|
|
||||||
echo "Original user: $(get_actual_user)"
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
|
||||||
echo "Mode: Interactive (prompts enabled)"
|
|
||||||
else
|
|
||||||
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if nvidia module is loaded
|
# Check if nvidia module is loaded
|
||||||
if ! lsmod | grep -q nvidia; then
|
if ! lsmod | grep -q nvidia; then
|
||||||
|
|||||||
50
scripts/lib/android.sh
Normal file
50
scripts/lib/android.sh
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Shared functions for Android-related scripts
|
||||||
|
# Source this file after sourcing common.sh
|
||||||
|
|
||||||
|
# Prevent multiple sourcing
|
||||||
|
[[ -n ${_LIB_ANDROID_LOADED:-} ]] && return 0
|
||||||
|
_LIB_ANDROID_LOADED=1
|
||||||
|
|
||||||
|
ANDROID_WORK_DIR="${HOME}/.cache/android-adblock"
|
||||||
|
ensure_dir "$ANDROID_WORK_DIR"
|
||||||
|
|
||||||
|
# Exit with error message
|
||||||
|
die() {
|
||||||
|
echo "[ERROR] $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print section header
|
||||||
|
print_header() {
|
||||||
|
echo
|
||||||
|
echo "========================================"
|
||||||
|
echo " $1"
|
||||||
|
echo "========================================"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if ADB device is connected
|
||||||
|
check_adb_device() {
|
||||||
|
log "Checking device connection..."
|
||||||
|
if ! adb devices | grep -q "device$"; then
|
||||||
|
die "No device connected. Enable USB debugging and connect your phone."
|
||||||
|
fi
|
||||||
|
log "Device connected"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if device has root access
|
||||||
|
check_adb_root() {
|
||||||
|
log "Checking root access..."
|
||||||
|
if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then
|
||||||
|
die "Root access not available. Make sure Magisk is installed and grant root to Shell."
|
||||||
|
fi
|
||||||
|
log "Root access confirmed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Re-exec with sudo if needed to read /etc/hosts
|
||||||
|
require_hosts_readable() {
|
||||||
|
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
|
||||||
|
exec sudo -E bash "$0" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@ -21,7 +21,7 @@ log_message() {
|
|||||||
local formatted
|
local formatted
|
||||||
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
|
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
|
||||||
echo "$formatted" >&2
|
echo "$formatted" >&2
|
||||||
if [[ -n "$log_file" ]]; then
|
if [[ -n $log_file ]]; then
|
||||||
echo "$formatted" >>"$log_file" 2>/dev/null || true
|
echo "$formatted" >>"$log_file" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -57,13 +57,23 @@ get_actual_user() {
|
|||||||
get_actual_user_home() {
|
get_actual_user_home() {
|
||||||
local user
|
local user
|
||||||
user=$(get_actual_user)
|
user=$(get_actual_user)
|
||||||
if [[ "$user" == "root" ]]; then
|
if [[ $user == "root" ]]; then
|
||||||
echo "/root"
|
echo "/root"
|
||||||
else
|
else
|
||||||
echo "/home/$user"
|
echo "/home/$user"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set both ACTUAL_USER and USER_HOME variables (common pattern)
|
||||||
|
# Usage: set_actual_user_vars
|
||||||
|
# echo "$ACTUAL_USER" # => the actual user
|
||||||
|
# echo "$USER_HOME" # => /home/username
|
||||||
|
set_actual_user_vars() {
|
||||||
|
ACTUAL_USER=$(get_actual_user)
|
||||||
|
USER_HOME=$(get_actual_user_home)
|
||||||
|
export ACTUAL_USER USER_HOME
|
||||||
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ARGUMENT PARSING HELPERS
|
# ARGUMENT PARSING HELPERS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -102,6 +112,32 @@ parse_interactive_args() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Handle common argument patterns for scripts with custom usage functions
|
||||||
|
# Usage: handle_arg_help_or_unknown "$1" usage_function err_function
|
||||||
|
# Returns: 0 if argument was handled (caller should continue), 1 if not our concern
|
||||||
|
# Exits: on -h/--help (exit 0) or unknown arg starting with - (exit 2)
|
||||||
|
handle_arg_help_or_unknown() {
|
||||||
|
local arg="$1"
|
||||||
|
local usage_fn="${2:-usage}"
|
||||||
|
local err_fn="${3:-err}"
|
||||||
|
|
||||||
|
case "$arg" in
|
||||||
|
-h | --help)
|
||||||
|
"$usage_fn"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
"$err_fn" "Unknown argument: $arg"
|
||||||
|
"$usage_fn"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1 # Not a flag, let caller handle it
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# FOCUS APP DETECTION (for digital wellbeing scripts)
|
# FOCUS APP DETECTION (for digital wellbeing scripts)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -172,6 +208,61 @@ require_command() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check for ImageMagick and display helpful installation message
|
||||||
|
# Usage: require_imagemagick [optional: "magick" or "convert"]
|
||||||
|
# Returns: Sets MAGICK_CMD variable to available command
|
||||||
|
require_imagemagick() {
|
||||||
|
local preferred="${1:-}"
|
||||||
|
|
||||||
|
if [[ $preferred == "magick" ]] || [[ -z $preferred ]]; then
|
||||||
|
if command -v magick &>/dev/null; then
|
||||||
|
MAGICK_CMD="magick"
|
||||||
|
export MAGICK_CMD
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $preferred == "convert" ]] || [[ -z $preferred ]]; then
|
||||||
|
if command -v convert &>/dev/null; then
|
||||||
|
MAGICK_CMD="convert"
|
||||||
|
export MAGICK_CMD
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Error: ImageMagick is not installed." >&2
|
||||||
|
echo "Install it with:" >&2
|
||||||
|
echo " Arch Linux: sudo pacman -S imagemagick" >&2
|
||||||
|
echo " Ubuntu/Debian: sudo apt install imagemagick" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install missing pacman packages
|
||||||
|
# Usage: install_missing_pacman_packages pkg1 pkg2 pkg3 ...
|
||||||
|
# Returns 0 if all packages installed successfully, 1 otherwise
|
||||||
|
install_missing_pacman_packages() {
|
||||||
|
local packages=("$@")
|
||||||
|
local missing=()
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
|
||||||
|
missing+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||||
|
echo "[INFO] All required packages are already installed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Installing missing packages: ${missing[*]}"
|
||||||
|
if ! sudo pacman -S --needed --noconfirm "${missing[@]}"; then
|
||||||
|
echo "[ERROR] Failed to install packages" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# NOTIFICATION
|
# NOTIFICATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@ -203,7 +294,7 @@ get_script_dir() {
|
|||||||
# Usage: ensure_dir "/path/to/dir"
|
# Usage: ensure_dir "/path/to/dir"
|
||||||
ensure_dir() {
|
ensure_dir() {
|
||||||
local dir="$1"
|
local dir="$1"
|
||||||
if [[ ! -d "$dir" ]]; then
|
if [[ ! -d $dir ]]; then
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -212,30 +303,176 @@ ensure_dir() {
|
|||||||
# SYSTEMD HELPERS
|
# SYSTEMD HELPERS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
# Internal helper for running systemctl with optional --user flag
|
||||||
|
_systemctl_cmd() {
|
||||||
|
local user_flag="$1"
|
||||||
|
shift
|
||||||
|
if [[ $user_flag == "--user" ]]; then
|
||||||
|
systemctl --user "$@"
|
||||||
|
else
|
||||||
|
systemctl "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Enable and start a systemd service (user or system)
|
# Enable and start a systemd service (user or system)
|
||||||
# Usage: enable_service "service-name" [--user]
|
# Usage: enable_service "service-name" [--user]
|
||||||
enable_service() {
|
enable_service() {
|
||||||
local service="$1"
|
local service="$1"
|
||||||
local user_flag="${2:-}"
|
local user_flag="${2:-}"
|
||||||
|
_systemctl_cmd "$user_flag" daemon-reload
|
||||||
if [[ "$user_flag" == "--user" ]]; then
|
_systemctl_cmd "$user_flag" enable --now "$service"
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable --now "$service"
|
|
||||||
else
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl enable --now "$service"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a systemd service is active
|
# Check if a systemd service is active
|
||||||
# Usage: if is_service_active "service-name" [--user]; then ...
|
# Usage: if is_service_active "service-name" [--user]; then ...
|
||||||
is_service_active() {
|
is_service_active() {
|
||||||
local service="$1"
|
_systemctl_cmd "${2:-}" is-active --quiet "$1"
|
||||||
local user_flag="${2:-}"
|
}
|
||||||
|
|
||||||
if [[ "$user_flag" == "--user" ]]; then
|
# Check if a systemd service is enabled
|
||||||
systemctl --user is-active --quiet "$service"
|
# Usage: if is_service_enabled "service-name" [--user]; then ...
|
||||||
|
is_service_enabled() {
|
||||||
|
_systemctl_cmd "${2:-}" is-enabled --quiet "$1" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# COLORED LOGGING (for scripts that need colored output)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
declare -g COLOR_RED='\033[1;31m'
|
||||||
|
declare -g COLOR_GREEN='\033[1;32m'
|
||||||
|
declare -g COLOR_YELLOW='\033[1;33m'
|
||||||
|
declare -g COLOR_BLUE='\033[1;34m'
|
||||||
|
declare -g COLOR_NC='\033[0m'
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
printf "${COLOR_BLUE}[INFO]${COLOR_NC} %s\n" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_ok() {
|
||||||
|
printf "${COLOR_GREEN}[ OK ]${COLOR_NC} %s\n" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
printf "${COLOR_YELLOW}[WARN]${COLOR_NC} %s\n" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
printf "${COLOR_RED}[ERROR]${COLOR_NC} %s\n" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Alias for compatibility
|
||||||
|
warn() { log_warn "$@"; }
|
||||||
|
err() { log_error "$@"; }
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INTERACTIVE PROMPTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Ask yes/no question, returns 0 for yes, 1 for no
|
||||||
|
# Usage: if ask_yes_no "Continue?"; then ...
|
||||||
|
ask_yes_no() {
|
||||||
|
local prompt="$1"
|
||||||
|
local ans
|
||||||
|
read -r -p "$prompt [y/N]: " ans || true
|
||||||
|
case "${ans:-}" in
|
||||||
|
y | Y | yes | YES) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if a command is available
|
||||||
|
# Usage: if has_cmd git; then ...
|
||||||
|
has_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# STANDARD SETUP HEADER
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Print a standard setup header for scripts
|
||||||
|
# Usage: print_setup_header "Script Name"
|
||||||
|
print_setup_header() {
|
||||||
|
local title="$1"
|
||||||
|
echo "$title"
|
||||||
|
printf '=%.0s' $(seq 1 ${#title})
|
||||||
|
echo ""
|
||||||
|
echo "Current Date: $(date)"
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Original user: $(get_actual_user)"
|
||||||
|
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||||
|
echo "Mode: Interactive (prompts enabled)"
|
||||||
else
|
else
|
||||||
systemctl is-active --quiet "$service"
|
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# MOUNT/UNMOUNT HELPERS (for hosts guard and similar)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Count mount layers for a path
|
||||||
|
# Usage: count=$(mount_layers_count "/etc/hosts")
|
||||||
|
mount_layers_count() {
|
||||||
|
local target="$1"
|
||||||
|
awk -v t="$target" '$5==t{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collapse all bind mount layers for a path
|
||||||
|
# Usage: collapse_mounts "/etc/hosts" [max_iterations]
|
||||||
|
collapse_mounts() {
|
||||||
|
local target="$1"
|
||||||
|
local max_iter="${2:-20}"
|
||||||
|
local i=0
|
||||||
|
|
||||||
|
if has_cmd mountpoint; then
|
||||||
|
while mountpoint -q "$target"; do
|
||||||
|
umount -l "$target" >/dev/null 2>&1 || break
|
||||||
|
i=$((i + 1))
|
||||||
|
((i >= max_iter)) && break
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local cnt
|
||||||
|
cnt=$(mount_layers_count "$target")
|
||||||
|
while ((cnt > 1)); do
|
||||||
|
umount -l "$target" >/dev/null 2>&1 || break
|
||||||
|
i=$((i + 1))
|
||||||
|
((i >= max_iter)) && break
|
||||||
|
cnt=$(mount_layers_count "$target")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RESOLUTION/FORMAT VALIDATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
|
# Usage: if validate_resolution "1920x1080"; then ...
|
||||||
|
validate_resolution() {
|
||||||
|
local res="$1"
|
||||||
|
[[ $res =~ ^[0-9]+x[0-9]+$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate output filename with suffix
|
||||||
|
# Usage: output=$(generate_output_filename "input.jpg" "_resized")
|
||||||
|
generate_output_filename() {
|
||||||
|
local input="$1"
|
||||||
|
local suffix="$2"
|
||||||
|
local ext="${3:-}"
|
||||||
|
|
||||||
|
local basename dirname filename extension
|
||||||
|
basename=$(basename "$input")
|
||||||
|
dirname=$(dirname "$input")
|
||||||
|
filename="${basename%.*}"
|
||||||
|
extension="${basename##*.}"
|
||||||
|
|
||||||
|
# Handle files without extension
|
||||||
|
if [[ $filename == "$extension" ]]; then
|
||||||
|
extension="${ext:-jpg}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${dirname}/${filename}${suffix}.${extension}"
|
||||||
|
}
|
||||||
|
|||||||
@ -17,6 +17,11 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
|
||||||
|
# Source common library for log_info, log_warn, log_error
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
DEFAULT_ROOT=$(cd -- "$SCRIPT_DIR/../../" && pwd)
|
DEFAULT_ROOT=$(cd -- "$SCRIPT_DIR/../../" && pwd)
|
||||||
|
|
||||||
ROOT_DIR="$DEFAULT_ROOT"
|
ROOT_DIR="$DEFAULT_ROOT"
|
||||||
@ -25,18 +30,6 @@ INSTALL_ONLY="false"
|
|||||||
LIST_ONLY="false"
|
LIST_ONLY="false"
|
||||||
VERBOSE="false"
|
VERBOSE="false"
|
||||||
|
|
||||||
log_info() {
|
|
||||||
printf '\033[1;34m[INFO]\033[0m %s\n' "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warn() {
|
|
||||||
printf '\033[1;33m[WARN]\033[0m %s\n' "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
printf '\033[1;31m[ERROR]\033[0m %s\n' "$*" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [options]
|
Usage: $(basename "$0") [options]
|
||||||
@ -47,7 +40,9 @@ Options:
|
|||||||
--install-only Only install linters, do not scan
|
--install-only Only install linters, do not scan
|
||||||
--list-only Only list discovered shell files, do not run linters
|
--list-only Only list discovered shell files, do not run linters
|
||||||
--verbose Print additional details while running
|
--verbose Print additional details while running
|
||||||
-h, --help Show this helpLinters used:
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Linters used:
|
||||||
Required: shellcheck, shfmt
|
Required: shellcheck, shfmt
|
||||||
Optional (if available): checkbashisms, bashate
|
Optional (if available): checkbashisms, bashate
|
||||||
Syntax checks: bash -n, zsh -n (if installed), sh/dash -n
|
Syntax checks: bash -n, zsh -n (if installed), sh/dash -n
|
||||||
|
|||||||
@ -18,15 +18,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
# Source common library for log functions
|
||||||
YELLOW="\033[1;33m"
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
RED="\033[1;31m"
|
# shellcheck source=../../lib/common.sh
|
||||||
BLUE="\033[1;34m"
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
NC="\033[0m"
|
|
||||||
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
||||||
log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
|
|
||||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
||||||
log_error() { echo -e "${RED}[ERR ]${NC} $*" 1>&2; }
|
|
||||||
|
|
||||||
DO_POLICY=false
|
DO_POLICY=false
|
||||||
SET_DEFAULT=false
|
SET_DEFAULT=false
|
||||||
|
|||||||
@ -23,16 +23,11 @@ set -euo pipefail
|
|||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
|
|
||||||
SCRIPT_NAME="$(basename "$0")"
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
GREEN="\033[1;32m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
BLUE="\033[1;34m"
|
|
||||||
NC="\033[0m"
|
|
||||||
|
|
||||||
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
# Source common library for log functions
|
||||||
log_ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
# shellcheck source=../../lib/common.sh
|
||||||
log_error() { echo -e "${RED}[ERR ]${NC} $*" 1>&2; }
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
|||||||
@ -11,15 +11,10 @@ set -euo pipefail
|
|||||||
# Bash/get_rnnoise_model.sh # interactive download
|
# Bash/get_rnnoise_model.sh # interactive download
|
||||||
# RN_TARGET_DIR=./models Bash/get_rnnoise_model.sh --yes
|
# RN_TARGET_DIR=./models Bash/get_rnnoise_model.sh --yes
|
||||||
|
|
||||||
ask_yes_no() {
|
# Source common library for shared functions
|
||||||
read -r -p "$1 [y/N]: " ans || true
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
case "${ans:-}" in
|
# shellcheck source=../../lib/common.sh
|
||||||
y | Y | yes | YES) return 0 ;;
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
has_cmd() { command -v "$1" > /dev/null 2>&1; }
|
|
||||||
|
|
||||||
YES=false
|
YES=false
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@ -58,21 +53,31 @@ if ! has_cmd curl && ! has_cmd wget; then
|
|||||||
exit 3
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Priority 1: explicit URL
|
# Helper: try to download a URL to destination, exit 0 on success
|
||||||
if [[ -n ${RN_URL:-} ]]; then
|
# Usage: try_download_model URL DEST
|
||||||
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
try_download_model() {
|
||||||
|
local url="$1"
|
||||||
|
local dest="$2"
|
||||||
|
local tmp
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
|
echo "Attempting to download RNNoise model from: $url" >&2
|
||||||
if has_cmd curl; then
|
if has_cmd curl; then
|
||||||
curl -fsSL "$RN_URL" -o "$tmp"
|
curl -fsSL "$url" -o "$tmp" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
wget -qO "$tmp" "$RN_URL"
|
wget -qO "$tmp" "$url" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -s $tmp ]]; then
|
if [[ -s $tmp ]]; then
|
||||||
mv "$tmp" "$dest"
|
mv "$tmp" "$dest"
|
||||||
echo "Saved RNNoise model to: $dest"
|
echo "Saved RNNoise model to: $dest" >&2
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
rm -f "$tmp" || true
|
rm -f "$tmp" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Priority 1: explicit URL
|
||||||
|
if [[ -n ${RN_URL:-} ]]; then
|
||||||
|
echo "Downloading RNNoise model from RN_URL: $RN_URL" >&2
|
||||||
|
try_download_model "$RN_URL" "$dest"
|
||||||
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
echo "Warning: RN_URL download failed; continuing to fallback sources." >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -85,26 +90,7 @@ NU_URLS=(
|
|||||||
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn"
|
"https://raw.githubusercontent.com/GregorR/rnnoise-nu/master/src/models/cb.rnnn"
|
||||||
)
|
)
|
||||||
for u in "${NU_URLS[@]}"; do
|
for u in "${NU_URLS[@]}"; do
|
||||||
echo "Attempting to download RNNoise model from: $u" >&2
|
try_download_model "$u" "$dest"
|
||||||
tmp=$(mktemp)
|
|
||||||
if has_cmd curl; then
|
|
||||||
if curl -fsSL "$u" -o "$tmp"; then
|
|
||||||
if [[ -s $tmp ]]; then
|
|
||||||
mv "$tmp" "$dest"
|
|
||||||
echo "Saved RNNoise model to: $dest" >&2
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if wget -qO "$tmp" "$u"; then
|
|
||||||
if [[ -s $tmp ]]; then
|
|
||||||
mv "$tmp" "$dest"
|
|
||||||
echo "Saved RNNoise model to: $dest" >&2
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
rm -f "$tmp" || true
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Priority 2b: arnndn-models fallback (richardpl)
|
# Priority 2b: arnndn-models fallback (richardpl)
|
||||||
@ -112,26 +98,7 @@ RNNDN_URLS=(
|
|||||||
"https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn"
|
"https://raw.githubusercontent.com/richardpl/arnndn-models/master/sh.rnnn"
|
||||||
)
|
)
|
||||||
for u in "${RNNDN_URLS[@]}"; do
|
for u in "${RNNDN_URLS[@]}"; do
|
||||||
echo "Attempting to download RNNoise model from: $u" >&2
|
try_download_model "$u" "$dest"
|
||||||
tmp=$(mktemp)
|
|
||||||
if has_cmd curl; then
|
|
||||||
if curl -fsSL "$u" -o "$tmp"; then
|
|
||||||
if [[ -s $tmp ]]; then
|
|
||||||
mv "$tmp" "$dest"
|
|
||||||
echo "Saved RNNoise model to: $dest" >&2
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if wget -qO "$tmp" "$u"; then
|
|
||||||
if [[ -s $tmp ]]; then
|
|
||||||
mv "$tmp" "$dest"
|
|
||||||
echo "Saved RNNoise model to: $dest" >&2
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
rm -f "$tmp" || true
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Priority 3: repo archives (rnnoise-nu and arnndn-models)
|
# Priority 3: repo archives (rnnoise-nu and arnndn-models)
|
||||||
|
|||||||
@ -7,20 +7,15 @@ set -euo pipefail
|
|||||||
# Tries distro packages first; if not suitable, offers to build from source.
|
# Tries distro packages first; if not suitable, offers to build from source.
|
||||||
# This script prints commands and asks for confirmation before building.
|
# This script prints commands and asks for confirmation before building.
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
|
|
||||||
print_info() {
|
print_info() {
|
||||||
echo "[info] $*"
|
echo "[info] $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
ask_yes_no() {
|
|
||||||
read -r -p "$1 [y/N]: " ans || true
|
|
||||||
case "${ans:-}" in
|
|
||||||
y | Y | yes | YES) return 0 ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
has_cmd() { command -v "$1" > /dev/null 2>&1; }
|
|
||||||
|
|
||||||
detect_distro() {
|
detect_distro() {
|
||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
|
|||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../../lib/common.sh"
|
||||||
|
|
||||||
SCRIPT_NAME="$(basename "$0")"
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
|
|
||||||
RED="\033[31m"
|
RED="\033[31m"
|
||||||
@ -21,35 +26,8 @@ error() {
|
|||||||
printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
printf "%b[%s]%b %s\n" "$RED" "$SCRIPT_NAME" "$RESET" "$*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
require_command() {
|
|
||||||
local cmd="$1"
|
|
||||||
local package_hint="${2:-}"
|
|
||||||
|
|
||||||
if ! command -v "$cmd" > /dev/null 2>&1; then
|
|
||||||
if [[ -n $package_hint ]]; then
|
|
||||||
error "Missing command '$cmd'. Try installing the package: $package_hint"
|
|
||||||
else
|
|
||||||
error "Missing command '$cmd'."
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_pacman_packages() {
|
ensure_pacman_packages() {
|
||||||
local packages=("python" "git" "curl" "jq" "code")
|
install_missing_pacman_packages python git curl jq code
|
||||||
local missing=()
|
|
||||||
for pkg in "${packages[@]}"; do
|
|
||||||
if ! pacman -Qi "$pkg" > /dev/null 2>&1; then
|
|
||||||
missing+=("$pkg")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if ((${#missing[@]} > 0)); then
|
|
||||||
info "Installing required packages with pacman: ${missing[*]}"
|
|
||||||
sudo pacman -S --needed --noconfirm "${missing[@]}"
|
|
||||||
else
|
|
||||||
info "All required pacman packages are already installed."
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
install_uv() {
|
install_uv() {
|
||||||
|
|||||||
@ -17,16 +17,7 @@ shift "$COMMON_ARGS_SHIFT"
|
|||||||
# Check for sudo privileges
|
# Check for sudo privileges
|
||||||
require_root "$@"
|
require_root "$@"
|
||||||
|
|
||||||
echo "Periodic System Setup - Pacman Wrapper & Hosts File"
|
print_setup_header "Periodic System Setup - Pacman Wrapper & Hosts File"
|
||||||
echo "==================================================="
|
|
||||||
echo "Current Date: $(date)"
|
|
||||||
echo "User: $USER"
|
|
||||||
echo "Original user: $(get_actual_user)"
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
|
||||||
echo "Mode: Interactive (prompts enabled)"
|
|
||||||
else
|
|
||||||
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the directory where this script is located
|
# Get the directory where this script is located
|
||||||
CONFIG_DIR="$(dirname "$SCRIPT_DIR")"
|
CONFIG_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
|||||||
@ -16,16 +16,7 @@ shift "$COMMON_ARGS_SHIFT"
|
|||||||
# Check for sudo privileges
|
# Check for sudo privileges
|
||||||
require_root "$@"
|
require_root "$@"
|
||||||
|
|
||||||
echo "Thorium Browser Auto-Startup Setup"
|
print_setup_header "Thorium Browser Auto-Startup Setup"
|
||||||
echo "=================================="
|
|
||||||
echo "Current Date: $(date)"
|
|
||||||
echo "User: $USER"
|
|
||||||
echo "Original user: $(get_actual_user)"
|
|
||||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
|
||||||
echo "Mode: Interactive (prompts enabled)"
|
|
||||||
else
|
|
||||||
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Target URL
|
# Target URL
|
||||||
TARGET_URL="https://www.fitatu.com/app/planner"
|
TARGET_URL="https://www.fitatu.com/app/planner"
|
||||||
|
|||||||
@ -7,6 +7,11 @@ set -euo pipefail
|
|||||||
# Convert video files to a target format (mp4 or webm) using ffmpeg.
|
# Convert video files to a target format (mp4 or webm) using ffmpeg.
|
||||||
# Accepts either a single video file or a directory (will recurse into subdirectories).
|
# Accepts either a single video file or a directory (will recurse into subdirectories).
|
||||||
|
|
||||||
|
# Source common library
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Default settings
|
# Default settings
|
||||||
TARGET_FORMAT="mp4"
|
TARGET_FORMAT="mp4"
|
||||||
CRF="" # Will be set based on format if not specified
|
CRF="" # Will be set based on format if not specified
|
||||||
@ -17,10 +22,6 @@ TARGET_PATH=""
|
|||||||
# Video extensions to search for
|
# Video extensions to search for
|
||||||
ALL_VIDEO_EXTENSIONS=("mp4" "webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
|
ALL_VIDEO_EXTENSIONS=("mp4" "webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
|
||||||
|
|
||||||
log() {
|
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage:
|
Usage:
|
||||||
@ -56,7 +57,7 @@ get_video_extensions_except() {
|
|||||||
local exclude="$1"
|
local exclude="$1"
|
||||||
local exts=()
|
local exts=()
|
||||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ "${ext,,}" != "${exclude,,}" ]]; then
|
if [[ ${ext,,} != "${exclude,,}" ]]; then
|
||||||
exts+=("$ext")
|
exts+=("$ext")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -69,7 +70,7 @@ is_video_file() {
|
|||||||
ext="${ext,,}" # lowercase
|
ext="${ext,,}" # lowercase
|
||||||
|
|
||||||
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ "$ext" == "${video_ext,,}" ]]; then
|
if [[ $ext == "${video_ext,,}" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -81,7 +82,7 @@ convert_video() {
|
|||||||
local output_file="${input_file%.*}.${TARGET_FORMAT}"
|
local output_file="${input_file%.*}.${TARGET_FORMAT}"
|
||||||
|
|
||||||
# Skip if output already exists
|
# Skip if output already exists
|
||||||
if [[ -f "$output_file" ]]; then
|
if [[ -f $output_file ]]; then
|
||||||
log "Skipping '$input_file': output '$output_file' already exists"
|
log "Skipping '$input_file': output '$output_file' already exists"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@ -91,12 +92,12 @@ convert_video() {
|
|||||||
local ffmpeg_args=()
|
local ffmpeg_args=()
|
||||||
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
|
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
|
||||||
|
|
||||||
if [[ "$TARGET_FORMAT" == "mp4" ]]; then
|
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
||||||
# H.264 codec for video and AAC for audio (maximum compatibility)
|
# H.264 codec for video and AAC for audio (maximum compatibility)
|
||||||
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
|
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
|
||||||
ffmpeg_args+=(-c:a aac -b:a 192k)
|
ffmpeg_args+=(-c:a aac -b:a 192k)
|
||||||
ffmpeg_args+=(-movflags +faststart)
|
ffmpeg_args+=(-movflags +faststart)
|
||||||
elif [[ "$TARGET_FORMAT" == "webm" ]]; then
|
elif [[ $TARGET_FORMAT == "webm" ]]; then
|
||||||
# VP9 codec for video and Opus for audio
|
# VP9 codec for video and Opus for audio
|
||||||
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
|
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
|
||||||
ffmpeg_args+=(-c:a libopus -b:a 128k)
|
ffmpeg_args+=(-c:a libopus -b:a 128k)
|
||||||
@ -107,13 +108,13 @@ convert_video() {
|
|||||||
if ffmpeg "${ffmpeg_args[@]}"; then
|
if ffmpeg "${ffmpeg_args[@]}"; then
|
||||||
log "Successfully converted '$input_file'"
|
log "Successfully converted '$input_file'"
|
||||||
|
|
||||||
if [[ "$DELETE_ORIGINAL" == true ]]; then
|
if [[ $DELETE_ORIGINAL == true ]]; then
|
||||||
log "Deleting original: '$input_file'"
|
log "Deleting original: '$input_file'"
|
||||||
rm "$input_file"
|
rm "$input_file"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "Error converting '$input_file'"
|
log "Error converting '$input_file'"
|
||||||
[[ -f "$output_file" ]] && rm "$output_file"
|
[[ -f $output_file ]] && rm "$output_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -129,8 +130,8 @@ process_directory() {
|
|||||||
local find_args=(-type f \()
|
local find_args=(-type f \()
|
||||||
local first=true
|
local first=true
|
||||||
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
|
||||||
if [[ "${ext,,}" != "${TARGET_FORMAT,,}" ]]; then
|
if [[ ${ext,,} != "${TARGET_FORMAT,,}" ]]; then
|
||||||
if [[ "$first" == true ]]; then
|
if [[ $first == true ]]; then
|
||||||
first=false
|
first=false
|
||||||
else
|
else
|
||||||
find_args+=(-o)
|
find_args+=(-o)
|
||||||
@ -159,7 +160,7 @@ parse_args() {
|
|||||||
case "$opt" in
|
case "$opt" in
|
||||||
f)
|
f)
|
||||||
TARGET_FORMAT="${OPTARG,,}"
|
TARGET_FORMAT="${OPTARG,,}"
|
||||||
if [[ "$TARGET_FORMAT" != "mp4" && "$TARGET_FORMAT" != "webm" ]]; then
|
if [[ $TARGET_FORMAT != "mp4" && $TARGET_FORMAT != "webm" ]]; then
|
||||||
echo "Error: Format must be 'mp4' or 'webm'" >&2
|
echo "Error: Format must be 'mp4' or 'webm'" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -194,8 +195,8 @@ parse_args() {
|
|||||||
TARGET_PATH="$1"
|
TARGET_PATH="$1"
|
||||||
|
|
||||||
# Set default CRF based on format if not specified
|
# Set default CRF based on format if not specified
|
||||||
if [[ -z "$CRF" ]]; then
|
if [[ -z $CRF ]]; then
|
||||||
if [[ "$TARGET_FORMAT" == "mp4" ]]; then
|
if [[ $TARGET_FORMAT == "mp4" ]]; then
|
||||||
CRF=23
|
CRF=23
|
||||||
else
|
else
|
||||||
CRF=30
|
CRF=30
|
||||||
@ -207,14 +208,14 @@ main() {
|
|||||||
ensure_ffmpeg
|
ensure_ffmpeg
|
||||||
parse_args "$@"
|
parse_args "$@"
|
||||||
|
|
||||||
if [[ ! -e "$TARGET_PATH" ]]; then
|
if [[ ! -e $TARGET_PATH ]]; then
|
||||||
echo "Error: Path '$TARGET_PATH' does not exist." >&2
|
echo "Error: Path '$TARGET_PATH' does not exist." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -f "$TARGET_PATH" ]]; then
|
if [[ -f $TARGET_PATH ]]; then
|
||||||
# Single file
|
# Single file
|
||||||
if [[ "${TARGET_PATH,,}" == *."$TARGET_FORMAT" ]]; then
|
if [[ ${TARGET_PATH,,} == *."$TARGET_FORMAT" ]]; then
|
||||||
log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping."
|
log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@ -225,7 +226,7 @@ main() {
|
|||||||
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
|
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
elif [[ -d "$TARGET_PATH" ]]; then
|
elif [[ -d $TARGET_PATH ]]; then
|
||||||
process_directory "$TARGET_PATH"
|
process_directory "$TARGET_PATH"
|
||||||
else
|
else
|
||||||
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
|
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
|
||||||
|
|||||||
@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Default resolution
|
# Default resolution
|
||||||
DEFAULT_RESOLUTION="320x240"
|
DEFAULT_RESOLUTION="320x240"
|
||||||
|
|
||||||
@ -30,13 +35,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check if ImageMagick is installed
|
# Check if ImageMagick is installed
|
||||||
if ! command -v convert &> /dev/null; then
|
require_imagemagick "convert" || exit 1
|
||||||
echo "Error: ImageMagick (convert) is not installed."
|
|
||||||
echo "Install it with:"
|
|
||||||
echo " Arch Linux: sudo pacman -S imagemagick"
|
|
||||||
echo " Ubuntu/Debian: sudo apt install imagemagick"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
@ -55,7 +54,7 @@ if [[ ! -f ${INPUT_IMAGE} ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate resolution format (WIDTHxHEIGHT)
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then
|
if ! validate_resolution "$RESOLUTION"; then
|
||||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||||
exit 1
|
exit 1
|
||||||
@ -63,19 +62,7 @@ fi
|
|||||||
|
|
||||||
# Generate output filename if not provided
|
# Generate output filename if not provided
|
||||||
if [[ -z ${OUTPUT_IMAGE} ]]; then
|
if [[ -z ${OUTPUT_IMAGE} ]]; then
|
||||||
# Extract filename without extension and extension
|
OUTPUT_IMAGE=$(generate_output_filename "${INPUT_IMAGE}" "_${RESOLUTION}")
|
||||||
BASENAME=$(basename "${INPUT_IMAGE}")
|
|
||||||
FILENAME="${BASENAME%.*}"
|
|
||||||
EXTENSION="${BASENAME##*.}"
|
|
||||||
|
|
||||||
# If no extension (single name file), default to jpg
|
|
||||||
if [[ ${FILENAME} == "${EXTENSION}" ]]; then
|
|
||||||
EXTENSION="jpg"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create output filename with resolution suffix
|
|
||||||
DIRNAME=$(dirname "${INPUT_IMAGE}")
|
|
||||||
OUTPUT_IMAGE="${DIRNAME}/${FILENAME}_${RESOLUTION}.${EXTENSION}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Perform the conversion
|
# Perform the conversion
|
||||||
|
|||||||
@ -7,14 +7,15 @@ set -euo pipefail
|
|||||||
# Convert one or more PDF files to image files using ImageMagick v7 `magick`.
|
# Convert one or more PDF files to image files using ImageMagick v7 `magick`.
|
||||||
# Default output format is jpg, but can be changed with -f.
|
# Default output format is jpg, but can be changed with -f.
|
||||||
|
|
||||||
|
# Source common library
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
OUTPUT_DIR=""
|
OUTPUT_DIR=""
|
||||||
OUTPUT_FORMAT="jpg"
|
OUTPUT_FORMAT="jpg"
|
||||||
PDF_FILES=()
|
PDF_FILES=()
|
||||||
|
|
||||||
log() {
|
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage:
|
Usage:
|
||||||
@ -35,10 +36,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
ensure_magick() {
|
ensure_magick() {
|
||||||
if ! command -v magick > /dev/null 2>&1; then
|
require_imagemagick "magick" || exit 1
|
||||||
echo "Error: 'magick' (ImageMagick v7) is not installed or not in PATH." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args() {
|
parse_args() {
|
||||||
|
|||||||
@ -214,7 +214,7 @@ setup_udev_rules() {
|
|||||||
# Install MTKClient udev rules if mtkclient is present
|
# Install MTKClient udev rules if mtkclient is present
|
||||||
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
if [[ -d "${WORK_DIR}/mtkclient" ]]; then
|
||||||
log "Installing MTKClient udev rules..."
|
log "Installing MTKClient udev rules..."
|
||||||
if [[ -d "$mtk_udev_dir" ]]; then
|
if [[ -d $mtk_udev_dir ]]; then
|
||||||
sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2>/dev/null || warn "Failed to copy MTKClient rules"
|
sudo cp "$mtk_udev_dir"/*.rules /etc/udev/rules.d/ 2>/dev/null || warn "Failed to copy MTKClient rules"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -634,7 +634,7 @@ install_mtkclient() {
|
|||||||
|
|
||||||
local mtk_dir="${WORK_DIR}/mtkclient"
|
local mtk_dir="${WORK_DIR}/mtkclient"
|
||||||
|
|
||||||
if [[ -d "$mtk_dir" && -f "$mtk_dir/mtk.py" ]]; then
|
if [[ -d $mtk_dir && -f "$mtk_dir/mtk.py" ]]; then
|
||||||
log "MTKClient already installed at $mtk_dir"
|
log "MTKClient already installed at $mtk_dir"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@ -667,7 +667,7 @@ extract_boot_with_mtkclient() {
|
|||||||
local boot_a_img="$WORK_DIR/boot_a.img"
|
local boot_a_img="$WORK_DIR/boot_a.img"
|
||||||
local vbmeta_a_img="$WORK_DIR/vbmeta_a.img"
|
local vbmeta_a_img="$WORK_DIR/vbmeta_a.img"
|
||||||
|
|
||||||
if [[ ! -d "$mtk_dir" ]]; then
|
if [[ ! -d $mtk_dir ]]; then
|
||||||
error "MTKClient not installed. Run: $SCRIPT_NAME install-mtk"
|
error "MTKClient not installed. Run: $SCRIPT_NAME install-mtk"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -6,43 +6,13 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
# shellcheck source=../lib/common.sh
|
# shellcheck source=../lib/common.sh
|
||||||
source "$SCRIPT_DIR/../lib/common.sh"
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
# shellcheck source=../lib/android.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/android.sh"
|
||||||
|
|
||||||
# Re-run with sudo if needed for reading /etc/hosts
|
# Re-run with sudo if needed for reading /etc/hosts
|
||||||
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
|
require_hosts_readable "$@"
|
||||||
exec sudo -E bash "$0" "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORK_DIR="${HOME}/.cache/android-adblock"
|
WORK_DIR="$ANDROID_WORK_DIR"
|
||||||
ensure_dir "$WORK_DIR"
|
|
||||||
|
|
||||||
die() {
|
|
||||||
echo "[ERROR] $*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
print_header() {
|
|
||||||
echo
|
|
||||||
echo "========================================"
|
|
||||||
echo " $1"
|
|
||||||
echo "========================================"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
check_device() {
|
|
||||||
log "Checking device connection..."
|
|
||||||
if ! adb devices | grep -q "device$"; then
|
|
||||||
die "No device connected. Enable USB debugging and connect your phone."
|
|
||||||
fi
|
|
||||||
log "Device connected"
|
|
||||||
}
|
|
||||||
|
|
||||||
check_root() {
|
|
||||||
log "Checking root access..."
|
|
||||||
if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then
|
|
||||||
die "Root access not available. Make sure Magisk is installed and grant root to Shell."
|
|
||||||
fi
|
|
||||||
log "Root access confirmed"
|
|
||||||
}
|
|
||||||
|
|
||||||
install_adaway() {
|
install_adaway() {
|
||||||
print_header "Installing AdAway"
|
print_header "Installing AdAway"
|
||||||
|
|||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Configuration -----------------------------------------------------------------
|
# Configuration -----------------------------------------------------------------
|
||||||
TARGET_SESSION_NAME="Xfce Session"
|
TARGET_SESSION_NAME="Xfce Session"
|
||||||
TARGET_PACKAGES=(
|
TARGET_PACKAGES=(
|
||||||
@ -20,42 +25,15 @@ error() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
require_command() {
|
|
||||||
local cmd="$1" pkg_hint="${2:-}"
|
|
||||||
if ! command -v "$cmd" > /dev/null 2>&1; then
|
|
||||||
if [[ -n $pkg_hint ]]; then
|
|
||||||
warn "Install '$pkg_hint' to obtain the '$cmd' command."
|
|
||||||
fi
|
|
||||||
error "Required command '$cmd' not found."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_pacman() {
|
ensure_pacman() {
|
||||||
require_command pacman "pacman"
|
require_command pacman "pacman" || error "Required command 'pacman' not found."
|
||||||
if ! grep -qi "arch" /etc/os-release 2>/dev/null; then
|
if ! grep -qi "arch" /etc/os-release 2>/dev/null; then
|
||||||
warn "This script was designed for Arch Linux; continuing anyway."
|
warn "This script was designed for Arch Linux; continuing anyway."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_packages() {
|
install_packages() {
|
||||||
local missing=()
|
install_missing_pacman_packages "${TARGET_PACKAGES[@]}"
|
||||||
for pkg in "${TARGET_PACKAGES[@]}"; do
|
|
||||||
if ! pacman -Qi "$pkg" > /dev/null 2>&1; then
|
|
||||||
missing+=("$pkg")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
|
||||||
info "All target packages are already installed."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v sudo > /dev/null 2>&1; then
|
|
||||||
error "sudo is required to install packages. Install sudo or run this script as root."
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Installing missing packages: ${missing[*]}"
|
|
||||||
sudo pacman -S --needed --noconfirm "${missing[@]}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
print_post_install_tips() {
|
print_post_install_tips() {
|
||||||
|
|||||||
@ -6,6 +6,11 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Source common library for shared functions
|
||||||
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
|
# shellcheck source=../lib/common.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
|
||||||
# Default resolution
|
# Default resolution
|
||||||
DEFAULT_RESOLUTION="320x240"
|
DEFAULT_RESOLUTION="320x240"
|
||||||
|
|
||||||
@ -30,17 +35,7 @@ EOF
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check if ImageMagick is installed and determine which command to use
|
# Check if ImageMagick is installed and determine which command to use
|
||||||
if command -v magick &> /dev/null; then
|
require_imagemagick || exit 1
|
||||||
MAGICK_CMD="magick"
|
|
||||||
elif command -v convert &> /dev/null; then
|
|
||||||
MAGICK_CMD="convert"
|
|
||||||
else
|
|
||||||
echo "Error: ImageMagick is not installed."
|
|
||||||
echo "Install it with:"
|
|
||||||
echo " Arch Linux: sudo pacman -S imagemagick"
|
|
||||||
echo " Ubuntu/Debian: sudo apt install imagemagick"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
if [[ $# -lt 1 ]]; then
|
if [[ $# -lt 1 ]]; then
|
||||||
@ -59,7 +54,7 @@ if [[ ! -f ${INPUT_FILE} ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate resolution format (WIDTHxHEIGHT)
|
# Validate resolution format (WIDTHxHEIGHT)
|
||||||
if [[ ! ${RESOLUTION} =~ ^[0-9]+x[0-9]+$ ]]; then
|
if ! validate_resolution "$RESOLUTION"; then
|
||||||
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
echo "Error: Invalid resolution format '${RESOLUTION}'"
|
||||||
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
echo "Expected format: WIDTHxHEIGHT (e.g., 320x240, 1920x1080)"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@ -6,31 +6,21 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||||
# shellcheck source=../lib/common.sh
|
# shellcheck source=../lib/common.sh
|
||||||
source "$SCRIPT_DIR/../lib/common.sh"
|
source "$SCRIPT_DIR/../lib/common.sh"
|
||||||
|
# shellcheck source=../lib/android.sh
|
||||||
|
source "$SCRIPT_DIR/../lib/android.sh"
|
||||||
|
|
||||||
# Re-run with sudo if needed for reading /etc/hosts
|
# Re-run with sudo if needed for reading /etc/hosts
|
||||||
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
|
require_hosts_readable "$@"
|
||||||
exec sudo -E bash "$0" "$@"
|
|
||||||
fi
|
|
||||||
|
|
||||||
WORK_DIR="${HOME}/.cache/android-adblock"
|
WORK_DIR="$ANDROID_WORK_DIR"
|
||||||
ensure_dir "$WORK_DIR"
|
|
||||||
|
|
||||||
die() {
|
|
||||||
echo "[ERROR] $*" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log "Updating Android hosts file from Linux configuration..."
|
log "Updating Android hosts file from Linux configuration..."
|
||||||
|
|
||||||
# Check device connection
|
# Check device connection
|
||||||
if ! adb devices | grep -q "device$"; then
|
check_adb_device
|
||||||
die "No device connected. Enable USB debugging and connect your phone."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check root access
|
# Check root access
|
||||||
if ! adb shell "su -c 'echo test'" 2>/dev/null | grep -q "test"; then
|
check_adb_root
|
||||||
die "Root access not available. Make sure Magisk is installed and grant root to Shell."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use the StevenBlack cache or /etc/hosts
|
# Use the StevenBlack cache or /etc/hosts
|
||||||
HOSTS_FILE="$WORK_DIR/hosts"
|
HOSTS_FILE="$WORK_DIR/hosts"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user