mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:03:01 +02:00
linux_configuration: WIP digital_wellbeing + pacman hosts-guard updates
Pre-existing local changes from a prior session, committed together for cleanup. Touches: - hosts/guard/: pacman pre-unlock / post-relock hook tweaks and the shared hosts-guard-common.sh helper. - scripts/digital_wellbeing/: block_compulsive_opening, music_parallelism, setup_midnight_shutdown, setup_pc_startup_monitor refinements. - scripts/digital_wellbeing/pacman/pacman_wrapper.sh: substantial rewrite. - scripts/lib/common.sh: shared helpers expanded. - tests/test_hosts_guard_pacman_integration.sh: new integration test.
This commit is contained in:
parent
00c383008a
commit
f84135f6d7
@ -43,6 +43,7 @@ HOOK
|
||||
|
||||
# Place helper scripts into a shared location
|
||||
install -d -m 755 /usr/local/share/hosts-guard
|
||||
install -m 755 "$SCRIPT_DIR/pacman-hooks/hosts-guard-common.sh" /usr/local/share/hosts-guard/
|
||||
install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-pre-unlock-hosts.sh" /usr/local/share/hosts-guard/
|
||||
install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-post-relock-hosts.sh" /usr/local/share/hosts-guard/
|
||||
|
||||
|
||||
@ -8,6 +8,13 @@ RESOLVED_CONF=/etc/systemd/resolved.conf
|
||||
RESOLVED_DROPIN=/etc/systemd/resolved.conf.d
|
||||
LOGTAG=hosts-guard-hook
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "hosts-guard pacman hook must run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if target has a read-only mount
|
||||
is_ro_mount() {
|
||||
findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro
|
||||
|
||||
@ -8,6 +8,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=hosts-guard-common.sh
|
||||
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||
|
||||
require_root
|
||||
|
||||
ENFORCE=/usr/local/sbin/enforce-hosts.sh
|
||||
ENFORCE_NSSWITCH=/usr/local/sbin/enforce-nsswitch.sh
|
||||
ENFORCE_RESOLVED=/usr/local/sbin/enforce-resolved.sh
|
||||
|
||||
@ -8,15 +8,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=hosts-guard-common.sh
|
||||
source "$SCRIPT_DIR/hosts-guard-common.sh"
|
||||
|
||||
require_root
|
||||
|
||||
log_hook "pre" "unlocking(start)"
|
||||
|
||||
# Remove protective attributes from all guarded files
|
||||
remove_all_guard_attrs
|
||||
sudo rm /etc/hosts
|
||||
|
||||
# Stop guard services (hosts, nsswitch, resolved watchers)
|
||||
stop_units_if_present
|
||||
|
||||
log_hook "pre" "unlocking(start)"
|
||||
|
||||
# Collapse any existing mount layers
|
||||
collapse_mounts
|
||||
|
||||
|
||||
@ -61,14 +61,14 @@ ensure_state_dir() {
|
||||
# Log message with timestamp
|
||||
log_message() {
|
||||
local msg
|
||||
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
|
||||
msg="$(printf '%(%Y-%m-%d %H:%M:%S)T' -1) - $1"
|
||||
echo "$msg" >&2
|
||||
echo "$msg" >>"$LOG_FILE" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Get current hour key (YYYY-MM-DD-HH format)
|
||||
get_hour_key() {
|
||||
date '+%Y-%m-%d-%H'
|
||||
printf '%(%Y-%m-%d-%H)T' -1
|
||||
}
|
||||
|
||||
# Get state file path for an app
|
||||
@ -179,7 +179,7 @@ kill_app() {
|
||||
is_autoclose_suspended() {
|
||||
local app="$1"
|
||||
local today
|
||||
today=$(date '+%Y-%m-%d')
|
||||
today=$(printf '%(%Y-%m-%d)T' -1)
|
||||
local suspend_file="$STATE_DIR/${app}.suspend-autoclose"
|
||||
|
||||
if [[ -f $suspend_file ]]; then
|
||||
@ -220,8 +220,8 @@ launch_with_timer() {
|
||||
# Give Electron apps time to fork before we start polling
|
||||
sleep 2
|
||||
|
||||
# Record state
|
||||
echo "$app_pid $(date +%s)" >"$running_file"
|
||||
# Record state (FORK-FREE: use printf %s for timestamp)
|
||||
echo "$app_pid $(printf '%(%s)T' -1)" >"$running_file"
|
||||
log_message "LAUNCHED: $app with PID $app_pid (auto-close in ${timeout_minutes}m)"
|
||||
|
||||
# Spawn the auto-close daemon in a completely detached subshell
|
||||
@ -256,7 +256,7 @@ launch_with_timer() {
|
||||
# Kill all matching processes (handles forked Electron children)
|
||||
kill_app "$real_binary"
|
||||
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - AUTO-CLOSED: $app after ${timeout_minutes}m" >>"$LOG_FILE" 2>/dev/null || true
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T - AUTO-CLOSED: %s after %dm\n' -1 "$app" "${timeout_minutes}" >>"$LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$running_file" 2>/dev/null || true
|
||||
|
||||
@ -11,8 +11,16 @@ 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"
|
||||
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
|
||||
# shellcheck source=../lib/common.sh
|
||||
source "$SCRIPT_DIR/../lib/common.sh"
|
||||
elif [[ -f "/usr/local/lib/common.sh" ]]; then
|
||||
# shellcheck source=/usr/local/lib/common.sh
|
||||
source "/usr/local/lib/common.sh"
|
||||
else
|
||||
echo "ERROR: common.sh library not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism"
|
||||
@ -70,24 +78,34 @@ MUSIC_SERVICES=(
|
||||
"pandora.com"
|
||||
)
|
||||
|
||||
# Check if any music service is running and return its details
|
||||
# Check if any music service is running and return its details (OPTIMIZED: batch pgrep calls)
|
||||
find_music_services() {
|
||||
local found_services=()
|
||||
|
||||
for service in "${MUSIC_SERVICES[@]}"; do
|
||||
# Check for browser tabs with music services
|
||||
# This checks window titles which usually contain the URL or tab title
|
||||
if command -v xdotool &> /dev/null; then
|
||||
if xdotool search --name "$service" &> /dev/null 2>&1; then
|
||||
found_services+=("$service (window)")
|
||||
fi
|
||||
fi
|
||||
# Single pgrep call with combined regex for all music services (NO FORK PER SERVICE)
|
||||
local music_pattern
|
||||
printf -v music_pattern '%s|' "${MUSIC_SERVICES[@]}"
|
||||
music_pattern="${music_pattern%|}" # strip trailing |
|
||||
|
||||
# Check for dedicated desktop apps
|
||||
if pgrep -i -f "$service" &> /dev/null; then
|
||||
found_services+=("$service (process)")
|
||||
# Check processes (single fork)
|
||||
local matching_services
|
||||
if matching_services=$(pgrep -i -f "$music_pattern" 2>/dev/null); then
|
||||
while read -r pid; do
|
||||
local proc_name
|
||||
proc_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
|
||||
found_services+=("$proc_name (process)")
|
||||
done <<< "$matching_services"
|
||||
fi
|
||||
|
||||
# Check windows (use optimized is_focus_app_running logic: single xdotool regex call)
|
||||
if command -v xdotool &> /dev/null && [[ ${#MUSIC_SERVICES[@]} -gt 0 ]]; then
|
||||
local xdotool_regex
|
||||
printf -v xdotool_regex '%s|' "${MUSIC_SERVICES[@]}"
|
||||
xdotool_regex="${xdotool_regex%|}" # strip trailing |
|
||||
if xdotool search --name "$xdotool_regex" &> /dev/null 2>&1; then
|
||||
found_services+=("music service (window)")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ ${#found_services[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${found_services[@]}"
|
||||
@ -185,6 +203,7 @@ notify_user() {
|
||||
|
||||
# Instant monitoring loop - uses polling at high frequency ONLY when focus app is detected
|
||||
# When focus app active: checks every 0.5s. When idle: checks every 3s. Reduces fork overhead.
|
||||
# OPTIMIZATION: Single batched pgrep call instead of multiple separate calls
|
||||
instant_monitor_loop() {
|
||||
log_message "=== Music Parallelism INSTANT Monitor Started ==="
|
||||
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
||||
@ -192,18 +211,14 @@ instant_monitor_loop() {
|
||||
log_message "Polling: 0.5s when focus app active, 3s when idle (optimized for lower fork overhead)"
|
||||
|
||||
while true; do
|
||||
# Only check if focus app is running
|
||||
# Only check if focus app is running (uses optimized is_focus_app_running from common.sh)
|
||||
if is_focus_app_running &> /dev/null; then
|
||||
# Instant kill youtube-music if detected
|
||||
if pgrep -f "youtube-music" &> /dev/null; then
|
||||
pkill -9 -f "youtube-music" 2> /dev/null || true
|
||||
log_message "INSTANT KILL: YouTube Music terminated"
|
||||
notify-send -u normal -t 2000 "🎵 YouTube Music killed" "Focus mode active" 2> /dev/null || true
|
||||
fi
|
||||
# Also check other music services
|
||||
if pgrep -x "spotify" &> /dev/null; then
|
||||
pkill -9 -x "spotify" 2> /dev/null || true
|
||||
log_message "INSTANT KILL: Spotify terminated"
|
||||
# OPTIMIZATION: Single pgrep call with regex instead of multiple calls
|
||||
# Kill youtube-music OR spotify with one command (use pkill to avoid pipe fork)
|
||||
if pgrep -i -f "youtube-music|spotify" &> /dev/null; then
|
||||
pkill -i -f "youtube-music|spotify" 2> /dev/null || true
|
||||
log_message "INSTANT KILL: Music services terminated"
|
||||
notify-send -u normal -t 2000 "🎵 Music killed" "Focus mode active" 2> /dev/null || true
|
||||
fi
|
||||
sleep "$FAST_CHECK_INTERVAL" # High-frequency check while focus app is active
|
||||
else
|
||||
@ -251,23 +266,27 @@ show_status() {
|
||||
echo "Focus Applications (window-based detection):"
|
||||
local focus_running=false
|
||||
|
||||
# Check windows
|
||||
if command -v xdotool &> /dev/null; then
|
||||
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
|
||||
if xdotool search --name "$app" &> /dev/null 2>&1; then
|
||||
echo " ✓ $app (WINDOW OPEN)"
|
||||
focus_running=true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Check processes
|
||||
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
|
||||
if pgrep -f "$app" &> /dev/null; then
|
||||
echo " ✓ $app (PROCESS RUNNING)"
|
||||
# Check windows (OPTIMIZED: single xdotool call with combined regex)
|
||||
if command -v xdotool &> /dev/null && [[ ${#FOCUS_APPS_WINDOWS[@]} -gt 0 ]]; then
|
||||
local regex
|
||||
printf -v regex '%s|' "${FOCUS_APPS_WINDOWS[@]}"
|
||||
regex="${regex%|}" # strip trailing |
|
||||
if xdotool search --name "$regex" &> /dev/null 2>&1; then
|
||||
echo " ✓ Focus window detected"
|
||||
focus_running=true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Check processes (OPTIMIZED: single pgrep call with combined regex)
|
||||
if [[ ${#FOCUS_APPS_PROCESSES[@]} -gt 0 ]]; then
|
||||
local proc_pattern
|
||||
printf -v proc_pattern '%s|' "${FOCUS_APPS_PROCESSES[@]}"
|
||||
proc_pattern="${proc_pattern%|}" # strip trailing |
|
||||
if pgrep -f "$proc_pattern" &> /dev/null; then
|
||||
echo " ✓ Focus process running"
|
||||
focus_running=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! $focus_running; then
|
||||
echo " (none detected)"
|
||||
|
||||
@ -73,37 +73,40 @@ load_policy_lists() {
|
||||
local whitelist_file="$script_dir/pacman_whitelist.txt"
|
||||
local greylist_file="$script_dir/pacman_greylist.txt"
|
||||
|
||||
read_policy_list_file() {
|
||||
local file_path="$1"
|
||||
local -n target_array="$2"
|
||||
local line=""
|
||||
|
||||
target_array=()
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
line="${line%$'\r'}"
|
||||
if [[ $line =~ ^[[:space:]]*(#|$) ]]; then
|
||||
continue
|
||||
fi
|
||||
target_array+=("${line,,}")
|
||||
done < "$file_path"
|
||||
}
|
||||
|
||||
if [[ -f $blocked_file ]]; then
|
||||
mapfile -t BLOCKED_KEYWORDS_LIST < <(sed 's/\r$//' "$blocked_file" | grep -Ev '^[[:space:]]*(#|$)' || true)
|
||||
read_policy_list_file "$blocked_file" BLOCKED_KEYWORDS_LIST
|
||||
else
|
||||
BLOCKED_KEYWORDS_LIST=()
|
||||
echo -e "${YELLOW}Warning:${NC} Missing blocked keywords file at $blocked_file" >&2
|
||||
fi
|
||||
|
||||
if [[ -f $whitelist_file ]]; then
|
||||
mapfile -t WHITELISTED_NAMES_LIST < <(sed 's/\r$//' "$whitelist_file" | grep -Ev '^[[:space:]]*(#|$)' || true)
|
||||
read_policy_list_file "$whitelist_file" WHITELISTED_NAMES_LIST
|
||||
else
|
||||
WHITELISTED_NAMES_LIST=()
|
||||
fi
|
||||
|
||||
if [[ -f $greylist_file ]]; then
|
||||
mapfile -t GREYLISTED_KEYWORDS_LIST < <(sed 's/\r$//' "$greylist_file" | grep -Ev '^[[:space:]]*(#|$)' || true)
|
||||
read_policy_list_file "$greylist_file" GREYLISTED_KEYWORDS_LIST
|
||||
else
|
||||
GREYLISTED_KEYWORDS_LIST=()
|
||||
fi
|
||||
|
||||
for i in "${!BLOCKED_KEYWORDS_LIST[@]}"; do
|
||||
BLOCKED_KEYWORDS_LIST[i]="${BLOCKED_KEYWORDS_LIST[i],,}"
|
||||
done
|
||||
|
||||
for i in "${!WHITELISTED_NAMES_LIST[@]}"; do
|
||||
WHITELISTED_NAMES_LIST[i]="${WHITELISTED_NAMES_LIST[i],,}"
|
||||
done
|
||||
|
||||
for i in "${!GREYLISTED_KEYWORDS_LIST[@]}"; do
|
||||
GREYLISTED_KEYWORDS_LIST[i]="${GREYLISTED_KEYWORDS_LIST[i],,}"
|
||||
done
|
||||
|
||||
POLICY_LISTS_LOADED=1
|
||||
}
|
||||
# Determine if this invocation may perform a transaction (upgrade/install/remove)
|
||||
@ -112,7 +115,22 @@ needs_unlock() {
|
||||
# Also include -Su/-Syu/-Syuu when -S is part of the combined flag
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-S* | -U | -R | --sync | --upgrade | --remove)
|
||||
-S*)
|
||||
return 0
|
||||
;;
|
||||
-U)
|
||||
return 0
|
||||
;;
|
||||
-R)
|
||||
return 0
|
||||
;;
|
||||
--sync)
|
||||
return 0
|
||||
;;
|
||||
--upgrade)
|
||||
return 0
|
||||
;;
|
||||
--remove)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
@ -120,6 +138,31 @@ needs_unlock() {
|
||||
return 1
|
||||
}
|
||||
|
||||
pacman_hooks_manage_hosts_guard() {
|
||||
local pre_hook="/etc/pacman.d/hooks/10-unlock-etc-hosts.hook"
|
||||
local post_hook="/etc/pacman.d/hooks/90-relock-etc-hosts.hook"
|
||||
local pre_exec="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh"
|
||||
local post_exec="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh"
|
||||
|
||||
if [[ ! -f $pre_hook || ! -f $post_hook ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
grep -Fq "$pre_exec" "$pre_hook" && grep -Fq "$post_exec" "$post_hook"
|
||||
}
|
||||
|
||||
should_use_wrapper_hosts_guard_fallback() {
|
||||
if ! needs_unlock "$@"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if pacman_hooks_manage_hosts_guard; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Run pre/post hooks for /etc/hosts guard if present
|
||||
pre_unlock_hosts() {
|
||||
local pre="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh"
|
||||
@ -203,22 +246,55 @@ function show_help() {
|
||||
# Function to display a message before executing
|
||||
function display_operation() {
|
||||
case "$1" in
|
||||
-S | -Sy | -S\ *)
|
||||
-S)
|
||||
echo -e "${BLUE}Installing packages...${NC}" >&2
|
||||
;;
|
||||
-Sy)
|
||||
echo -e "${BLUE}Installing packages...${NC}" >&2
|
||||
;;
|
||||
-S\ *)
|
||||
echo -e "${BLUE}Installing packages...${NC}" >&2
|
||||
;;
|
||||
-Syu | -Syyu)
|
||||
echo -e "${BLUE}Updating system...${NC}" >&2
|
||||
;;
|
||||
-R | -Rs | -Rns | -R\ *)
|
||||
-R)
|
||||
echo -e "${YELLOW}Removing packages...${NC}" >&2
|
||||
;;
|
||||
-Ss | -Ss\ *)
|
||||
-Rs)
|
||||
echo -e "${YELLOW}Removing packages...${NC}" >&2
|
||||
;;
|
||||
-Rns)
|
||||
echo -e "${YELLOW}Removing packages...${NC}" >&2
|
||||
;;
|
||||
-R\ *)
|
||||
echo -e "${YELLOW}Removing packages...${NC}" >&2
|
||||
;;
|
||||
-Ss)
|
||||
echo -e "${CYAN}Searching for packages...${NC}" >&2
|
||||
;;
|
||||
-Q | -Qs | -Qi | -Ql | -Q\ *)
|
||||
-Ss\ *)
|
||||
echo -e "${CYAN}Searching for packages...${NC}" >&2
|
||||
;;
|
||||
-Q)
|
||||
echo -e "${CYAN}Querying package database...${NC}" >&2
|
||||
;;
|
||||
-U | -U\ *)
|
||||
-Qs)
|
||||
echo -e "${CYAN}Querying package database...${NC}" >&2
|
||||
;;
|
||||
-Qi)
|
||||
echo -e "${CYAN}Querying package database...${NC}" >&2
|
||||
;;
|
||||
-Ql)
|
||||
echo -e "${CYAN}Querying package database...${NC}" >&2
|
||||
;;
|
||||
-Q\ *)
|
||||
echo -e "${CYAN}Querying package database...${NC}" >&2
|
||||
;;
|
||||
-U)
|
||||
echo -e "${BLUE}Installing local packages...${NC}" >&2
|
||||
;;
|
||||
-U\ *)
|
||||
echo -e "${BLUE}Installing local packages...${NC}" >&2
|
||||
;;
|
||||
-Scc)
|
||||
@ -280,9 +356,9 @@ 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)
|
||||
read -r -a holders <<< "$(fuser "$lock_file" 2>/dev/null || 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)
|
||||
mapfile -t holders < <(lsof -t "$lock_file" 2>/dev/null || true)
|
||||
fi
|
||||
# Filter out our own PID
|
||||
if [[ ${#holders[@]} -gt 0 ]]; then
|
||||
@ -677,20 +753,23 @@ echo -e "${GREEN}Executing:${NC} $PACMAN_BIN $*" >&2
|
||||
# Record start time for statistics
|
||||
start_time=$(date +%s)
|
||||
|
||||
# Execute the real pacman command (with /etc/hosts guard handling)
|
||||
if needs_unlock "$@"; then
|
||||
pre_unlock_hosts
|
||||
fi
|
||||
|
||||
# Handle a possible stale DB lock before executing
|
||||
if ! check_and_handle_db_lock "$@"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
manual_hosts_guard=0
|
||||
|
||||
# Execute the real pacman command (with /etc/hosts guard handling)
|
||||
if should_use_wrapper_hosts_guard_fallback "$@"; then
|
||||
pre_unlock_hosts
|
||||
manual_hosts_guard=1
|
||||
fi
|
||||
|
||||
"$PACMAN_BIN" "$@"
|
||||
exit_code=$?
|
||||
|
||||
if needs_unlock "$@"; then
|
||||
if [[ $manual_hosts_guard -eq 1 ]]; then
|
||||
post_relock_hosts
|
||||
fi
|
||||
|
||||
|
||||
@ -829,19 +829,19 @@ if [[ -z "${MORNING_END_HOUR:-}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current time and day
|
||||
current_hour=$(date +%H)
|
||||
current_minute=$(date +%M)
|
||||
# Get current time and day (fork-free bash builtins)
|
||||
current_hour=$(printf '%(%H)T' -1)
|
||||
current_minute=$(printf '%(%M)T' -1)
|
||||
current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute))
|
||||
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
||||
day_name=$(date +%A)
|
||||
day_of_week=$(printf '%(%u)T' -1) # 1=Monday, 7=Sunday
|
||||
day_name=$(printf '%(%A)T' -1)
|
||||
|
||||
# Calculate minute thresholds from config
|
||||
mon_wed_minutes=$((MON_WED_HOUR * 60))
|
||||
thu_sun_minutes=$((THU_SUN_HOUR * 60))
|
||||
morning_end_minutes=$((MORNING_END_HOUR * 60))
|
||||
|
||||
logger -t day-specific-shutdown "Checking shutdown conditions at $(date) - Day: $day_name ($day_of_week), Time: $current_hour:$current_minute"
|
||||
logger -t day-specific-shutdown "Checking shutdown conditions at $(printf '%(%Y-%m-%d %H:%M:%S)T' -1) - Day: $day_name ($day_of_week), Time: $current_hour:$current_minute"
|
||||
|
||||
# Determine if we should shutdown based on day and time
|
||||
should_shutdown=false
|
||||
@ -879,11 +879,11 @@ else
|
||||
fi
|
||||
|
||||
if [[ $should_shutdown == true ]]; then
|
||||
echo "$(date): Executing shutdown - current time $current_hour:$current_minute is within shutdown window for $day_name"
|
||||
logger -t day-specific-shutdown "Executing scheduled shutdown at $(date)"
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T: Executing shutdown - current time %s:%s is within shutdown window for %s\n' -1 "$current_hour" "$current_minute" "$day_name"
|
||||
logger -t day-specific-shutdown "Executing scheduled shutdown at $(printf '%(%Y-%m-%d %H:%M:%S)T' -1)"
|
||||
/usr/bin/systemctl poweroff
|
||||
else
|
||||
echo "$(date): Skipping shutdown - not within shutdown window for $day_name (current: $current_hour:$current_minute)"
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T: Skipping shutdown - not within shutdown window for %s (current: %s:%s)\n' -1 "$day_name" "$current_hour" "$current_minute"
|
||||
logger -t day-specific-shutdown "Skipped shutdown - not within shutdown window for $day_name (current: $current_hour:$current_minute)"
|
||||
fi
|
||||
EOF
|
||||
|
||||
@ -16,7 +16,7 @@ shift "$COMMON_ARGS_SHIFT"
|
||||
|
||||
echo "PC Startup Time Monitor for Arch Linux"
|
||||
echo "======================================"
|
||||
echo "Current Date: $(date)"
|
||||
echo "Current Date: $(get_datetime)"
|
||||
echo "User: $(get_actual_user)"
|
||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||
echo "Mode: Interactive (prompts enabled)"
|
||||
@ -33,91 +33,36 @@ echo "User home: $USER_HOME"
|
||||
|
||||
# Function to check if today is a monitored day
|
||||
is_monitored_day() {
|
||||
local day_of_week
|
||||
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
||||
|
||||
# Check if today is Monday (1), Friday (5), Saturday (6), or Sunday (7)
|
||||
if [[ $day_of_week == "1" ]] || [[ $day_of_week == "5" ]] || [[ $day_of_week == "6" ]] || [[ $day_of_week == "7" ]]; then
|
||||
return 0 # Yes, it's a monitored day
|
||||
else
|
||||
return 1 # No, it's not a monitored day
|
||||
fi
|
||||
is_day_of_week 1 5 6 7 # 1=Monday, 5=Friday, 6=Saturday, 7=Sunday
|
||||
}
|
||||
|
||||
# Function to check if current time is between 5AM and 8AM
|
||||
is_current_time_in_window() {
|
||||
local current_hour current_hour_num
|
||||
current_hour=$(date +%H)
|
||||
current_hour_num=$((10#$current_hour)) # Convert to decimal to avoid octal issues
|
||||
|
||||
if [[ $current_hour_num -ge 5 ]] && [[ $current_hour_num -lt 8 ]]; then
|
||||
return 0 # Yes, current time is in the 5AM-8AM window
|
||||
else
|
||||
return 1 # No, current time is outside the window
|
||||
fi
|
||||
is_hour_in_range 5 8
|
||||
}
|
||||
|
||||
# Function to check if PC was booted between 5AM-8AM today
|
||||
was_booted_in_window_today() {
|
||||
local today boot_time
|
||||
today=$(date +%Y-%m-%d)
|
||||
boot_time=""
|
||||
local boot_datetime boot_date boot_hour boot_hour_num today
|
||||
today=$(get_date)
|
||||
boot_datetime=$(get_boot_datetime)
|
||||
|
||||
# Get the last boot time using multiple methods for reliability
|
||||
if command -v uptime &>/dev/null; then
|
||||
# Method 1: Calculate boot time from uptime
|
||||
local uptime_seconds
|
||||
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
|
||||
if [[ $uptime_seconds -gt 0 ]]; then
|
||||
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 2: Use systemd if available (fallback)
|
||||
if [[ -z $boot_time ]] && command -v systemctl &>/dev/null; then
|
||||
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2>/dev/null || echo "")
|
||||
if [[ -n $boot_time ]]; then
|
||||
# This gives us relative time, need to calculate absolute time
|
||||
local current_time uptime_sec
|
||||
current_time=$(date +%s)
|
||||
uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
|
||||
boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 3: Use who -b (fallback)
|
||||
if [[ -z $boot_time ]] && command -v who &>/dev/null; then
|
||||
boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "")
|
||||
if [[ -n $boot_time ]]; then
|
||||
boot_time="$today $boot_time"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 4: Use /proc/uptime as final fallback
|
||||
if [[ -z $boot_time ]]; then
|
||||
local uptime_seconds
|
||||
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
|
||||
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
|
||||
fi
|
||||
|
||||
echo "Boot time detected: $boot_time"
|
||||
echo "Boot time detected: $boot_datetime"
|
||||
|
||||
# Check if boot time is from today
|
||||
local boot_date
|
||||
boot_date=$(echo "$boot_time" | cut -d' ' -f1)
|
||||
boot_date=$(echo "$boot_datetime" | cut -d' ' -f1)
|
||||
if [[ $boot_date != "$today" ]]; then
|
||||
echo "PC was not booted today (boot date: $boot_date, today: $today)"
|
||||
return 1 # Not booted today
|
||||
fi
|
||||
|
||||
# Extract hour from boot time
|
||||
local boot_hour boot_hour_num
|
||||
boot_hour=$(echo "$boot_time" | cut -d' ' -f2 | cut -d':' -f1)
|
||||
boot_hour=$(echo "$boot_datetime" | cut -d' ' -f2 | cut -d':' -f1)
|
||||
boot_hour_num=$((10#$boot_hour)) # Convert to decimal
|
||||
|
||||
echo "Boot hour: $boot_hour_num"
|
||||
|
||||
# Check if boot time was between 5AM (5) and 8AM (7, since we want before 8AM)
|
||||
# Check if boot time was between 5AM (5) and 8AM (8, before 8AM)
|
||||
if [[ $boot_hour_num -ge 5 ]] && [[ $boot_hour_num -lt 8 ]]; then
|
||||
echo "PC was booted in the expected window (5AM-8AM)"
|
||||
return 0 # Yes, booted in window
|
||||
@ -130,9 +75,9 @@ was_booted_in_window_today() {
|
||||
# Function to show notification/warning
|
||||
show_startup_warning() {
|
||||
local day_name current_time today
|
||||
day_name=$(date +%A)
|
||||
current_time=$(date +"%H:%M")
|
||||
today=$(date +%Y-%m-%d)
|
||||
day_name=$(get_day_name)
|
||||
current_time=$(printf '%(%H:%M)T' -1)
|
||||
today=$(get_date)
|
||||
|
||||
echo ""
|
||||
echo "⚠️ PC STARTUP TIME WARNING"
|
||||
|
||||
@ -385,6 +385,125 @@ log_error() {
|
||||
warn() { log_warn "$@"; }
|
||||
err() { log_error "$@"; }
|
||||
|
||||
# =============================================================================
|
||||
# EFFICIENT TIME FUNCTIONS (zero-fork bash builtins)
|
||||
# =============================================================================
|
||||
# These functions use printf '%(...)'T' bash builtin (NO external commands)
|
||||
# to avoid fork-storm anti-patterns in polling scripts.
|
||||
# See: .github/skills/efficient-polling-scripts/SKILL.md
|
||||
|
||||
# Get current Unix timestamp (seconds since epoch)
|
||||
# Usage: ts=$(get_timestamp)
|
||||
# FORK-FREE: uses bash builtin printf %s (sec_since_epoch)
|
||||
get_timestamp() {
|
||||
printf '%(%s)T' -1
|
||||
}
|
||||
|
||||
# Get current date in YYYY-MM-DD format
|
||||
# Usage: date=$(get_date)
|
||||
get_date() {
|
||||
printf '%(%Y-%m-%d)T' -1
|
||||
}
|
||||
|
||||
# Get current time in HH:MM:SS format
|
||||
# Usage: time=$(get_time)
|
||||
get_time() {
|
||||
printf '%(%H:%M:%S)T' -1
|
||||
}
|
||||
|
||||
# Get current date-time in YYYY-MM-DD HH:MM:SS format
|
||||
# Usage: dt=$(get_datetime)
|
||||
get_datetime() {
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T' -1
|
||||
}
|
||||
|
||||
# Get day of week (1=Monday, 7=Sunday)
|
||||
# Usage: dow=$(get_day_of_week)
|
||||
get_day_of_week() {
|
||||
printf '%(%u)T' -1
|
||||
}
|
||||
|
||||
# Get day name (Monday, Tuesday, ...)
|
||||
# Usage: day=$(get_day_name)
|
||||
get_day_name() {
|
||||
printf '%(%A)T' -1
|
||||
}
|
||||
|
||||
# Get current hour (00-23)
|
||||
# Usage: hour=$(get_hour)
|
||||
get_hour() {
|
||||
printf '%(%H)T' -1
|
||||
}
|
||||
|
||||
# Get current minute (00-59)
|
||||
# Usage: minute=$(get_minute)
|
||||
get_minute() {
|
||||
printf '%(%M)T' -1
|
||||
}
|
||||
|
||||
# Get current second (00-59)
|
||||
# Usage: second=$(get_second)
|
||||
get_second() {
|
||||
printf '%(%S)T' -1
|
||||
}
|
||||
|
||||
# Get Unix timestamp from boot (uptime in seconds)
|
||||
# Usage: boot_seconds=$(get_uptime_seconds)
|
||||
get_uptime_seconds() {
|
||||
read -r uptime_with_fraction _ < /proc/uptime
|
||||
printf '%.*f\n' 0 "$uptime_with_fraction"
|
||||
}
|
||||
|
||||
# Get boot time in YYYY-MM-DD HH:MM:SS format
|
||||
# Usage: boot_time=$(get_boot_datetime)
|
||||
# Calculates: current_time - uptime_seconds
|
||||
get_boot_datetime() {
|
||||
local uptime_seconds
|
||||
uptime_seconds=$(get_uptime_seconds)
|
||||
local boot_ts=$(($(get_timestamp) - uptime_seconds))
|
||||
printf '%(%Y-%m-%d %H:%M:%S)T' "$boot_ts"
|
||||
}
|
||||
|
||||
# Get boot time date only (YYYY-MM-DD)
|
||||
# Usage: boot_date=$(get_boot_date)
|
||||
get_boot_date() {
|
||||
local uptime_seconds
|
||||
uptime_seconds=$(get_uptime_seconds)
|
||||
local boot_ts=$(($(get_timestamp) - uptime_seconds))
|
||||
printf '%(%Y-%m-%d)T' "$boot_ts"
|
||||
}
|
||||
|
||||
# Get boot time hour only (00-23)
|
||||
# Usage: boot_hour=$(get_boot_hour)
|
||||
get_boot_hour() {
|
||||
local uptime_seconds
|
||||
uptime_seconds=$(get_uptime_seconds)
|
||||
local boot_ts=$(($(get_timestamp) - uptime_seconds))
|
||||
printf '%(%H)T' "$boot_ts"
|
||||
}
|
||||
|
||||
# Check if current time is within a given hour range
|
||||
# Usage: if is_hour_in_range 5 8; then ... # 5AM-8AM
|
||||
is_hour_in_range() {
|
||||
local start_hour=$1
|
||||
local end_hour=$2
|
||||
local current_hour
|
||||
current_hour=$(get_hour)
|
||||
local current_hour_num=$((10#$current_hour))
|
||||
[[ $current_hour_num -ge $start_hour ]] && [[ $current_hour_num -lt $end_hour ]]
|
||||
}
|
||||
|
||||
# Check if current day is a specific day of week
|
||||
# Usage: if is_day_of_week 1 5 6 7; then ... # Monday, Friday, Saturday, Sunday
|
||||
is_day_of_week() {
|
||||
local target_day
|
||||
target_day=$(get_day_of_week)
|
||||
for day in "$@"; do
|
||||
[[ $target_day -eq $day ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# INTERACTIVE PROMPTS
|
||||
# =============================================================================
|
||||
@ -415,10 +534,12 @@ has_cmd() {
|
||||
# Usage: print_setup_header "Script Name"
|
||||
print_setup_header() {
|
||||
local title="$1"
|
||||
local current_datetime
|
||||
current_datetime=$(get_datetime)
|
||||
echo "$title"
|
||||
printf '=%.0s' $(seq 1 ${#title})
|
||||
echo ""
|
||||
echo "Current Date: $(date)"
|
||||
echo "Current Date: $current_datetime"
|
||||
echo "User: $USER"
|
||||
echo "Original user: $(get_actual_user)"
|
||||
if [[ $INTERACTIVE_MODE == "true" ]]; then
|
||||
|
||||
93
linux_configuration/tests/test_hosts_guard_pacman_integration.sh
Executable file
93
linux_configuration/tests/test_hosts_guard_pacman_integration.sh
Executable file
@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for pacman wrapper and hosts-guard hook integration.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
WRAPPER_FILE="$REPO_DIR/scripts/digital_wellbeing/pacman/pacman_wrapper.sh"
|
||||
PRE_HOOK_FILE="$REPO_DIR/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh"
|
||||
POST_HOOK_FILE="$REPO_DIR/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh"
|
||||
COMMON_FILE="$REPO_DIR/hosts/guard/pacman-hooks/hosts-guard-common.sh"
|
||||
INSTALLER_FILE="$REPO_DIR/hosts/guard/install_pacman_hooks.sh"
|
||||
|
||||
assert_contains() {
|
||||
local file_path="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
|
||||
if grep -Fq "$pattern" "$file_path"; then
|
||||
echo "PASS: $message"
|
||||
else
|
||||
echo "FAIL: $message"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_regex() {
|
||||
local file_path="$1"
|
||||
local pattern="$2"
|
||||
local message="$3"
|
||||
|
||||
if grep -Eq "$pattern" "$file_path"; then
|
||||
echo "FAIL: $message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "PASS: $message"
|
||||
}
|
||||
|
||||
first_line_number() {
|
||||
local file_path="$1"
|
||||
local pattern="$2"
|
||||
|
||||
grep -n -F -m 1 "$pattern" "$file_path" | cut -d: -f1
|
||||
}
|
||||
|
||||
assert_order() {
|
||||
local file_path="$1"
|
||||
local first_pattern="$2"
|
||||
local second_pattern="$3"
|
||||
local message="$4"
|
||||
local first_line
|
||||
local second_line
|
||||
|
||||
first_line="$(first_line_number "$file_path" "$first_pattern")"
|
||||
second_line="$(first_line_number "$file_path" "$second_pattern")"
|
||||
|
||||
if [[ -z "$first_line" || -z "$second_line" ]]; then
|
||||
echo "FAIL: $message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( first_line < second_line )); then
|
||||
echo "PASS: $message"
|
||||
else
|
||||
echo "FAIL: $message"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Hosts guard pacman integration regression tests ==="
|
||||
|
||||
for file_path in "$WRAPPER_FILE" "$PRE_HOOK_FILE" "$POST_HOOK_FILE" "$COMMON_FILE" "$INSTALLER_FILE"; do
|
||||
bash -n "$file_path"
|
||||
done
|
||||
echo "PASS: shell syntax is valid"
|
||||
|
||||
assert_not_regex "$PRE_HOOK_FILE" '(^|[[:space:]])(sudo[[:space:]]+)?rm[[:space:]]+/etc/hosts([[:space:]]|$)' \
|
||||
"pre-transaction hook must not delete /etc/hosts"
|
||||
|
||||
assert_contains "$WRAPPER_FILE" 'pacman_hooks_manage_hosts_guard()' \
|
||||
"wrapper detects when pacman hooks already manage hosts guard"
|
||||
assert_contains "$WRAPPER_FILE" 'should_use_wrapper_hosts_guard_fallback()' \
|
||||
"wrapper exposes a dedicated fallback path for hosts guard"
|
||||
assert_order "$WRAPPER_FILE" 'if ! check_and_handle_db_lock "$@"; then' 'if should_use_wrapper_hosts_guard_fallback "$@"; then' \
|
||||
"wrapper checks pacman db lock before any manual hosts unlock fallback"
|
||||
assert_contains "$WRAPPER_FILE" 'manual_hosts_guard=1' \
|
||||
"wrapper tracks whether manual hosts guard fallback was used"
|
||||
|
||||
assert_contains "$INSTALLER_FILE" 'install -m 755 "$SCRIPT_DIR/pacman-hooks/hosts-guard-common.sh" /usr/local/share/hosts-guard/' \
|
||||
"installer deploys shared hosts guard hook helpers"
|
||||
|
||||
echo "All hosts guard pacman integration regression tests passed."
|
||||
Loading…
Reference in New Issue
Block a user