mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 14:23:16 +02:00
Optimize polling/runtime scripts, add regressions, and sync verification artifacts
This commit is contained in:
parent
f4a188068f
commit
c9923542fc
@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "PC polling/runtime validation and deployment sync",
|
||||
"objective": "Ensure PC-facing monitoring scripts are optimized, validated, and actually running from deployed runtime paths rather than only modified in-repo files.",
|
||||
"acceptance_criteria": [
|
||||
"Changed-file pre-commit checks pass with no remaining hook failures",
|
||||
"Updated scripts are synced to runtime paths (~/.config/i3blocks and /usr/local/bin where applicable)",
|
||||
"Runtime process evidence shows active i3blocks + music-parallelism + focus-mode daemons"
|
||||
],
|
||||
"out_of_scope": [
|
||||
"Repository-wide legacy lint/polling findings in untouched files",
|
||||
"Android device runtime verification outside PC scope"
|
||||
],
|
||||
"verifier": "pre-commit hooks, shell regression tests, and runtime process inspection commands"
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"intent": "Reduce desktop polling overhead and ensure updated Linux monitoring scripts are actually active on the PC runtime paths.",
|
||||
"scope": [
|
||||
"linux_configuration i3blocks persist scripts and digital wellbeing daemon scripts",
|
||||
"Runtime deployment sync to ~/.config/i3blocks and /usr/local/bin",
|
||||
"Non-goal: fix unrelated repo-wide historical lint/polling findings"
|
||||
],
|
||||
"changes": [
|
||||
"Optimized i3blocks activitywatch and warp polling intervals and synced live ~/.config script paths",
|
||||
"Optimized android_guardian service/post-fs-data scripts with throttled checks and lower-fork comparisons, with new shell regressions",
|
||||
"Deployed updated /usr/local/bin/music-parallelism.sh and restarted user service"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "pre-commit run --files $(git diff --name-only) $(git ls-files --others --exclude-standard)",
|
||||
"result": "pass",
|
||||
"evidence": "All hooks passed for changed files including no-polling-antipatterns, ruff, shellcheck, and secrets checks."
|
||||
},
|
||||
{
|
||||
"command": "bash linux_configuration/tests/test_i3blocks_efficiency.sh",
|
||||
"result": "pass",
|
||||
"evidence": "i3blocks efficiency regression suite passed after interval updates."
|
||||
},
|
||||
{
|
||||
"command": "ps -eo pid,ppid,comm,args --sort=comm | grep -E 'music-parallelism|focus-mode-daemon|i3blocks|activitywatch_status'",
|
||||
"result": "pass",
|
||||
"evidence": "Confirmed running i3blocks tree, focus-mode daemon, and music-parallelism service process."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Longer polling intervals may delay status updates for warp/activitywatch",
|
||||
"Live deployment drift can recur if repo edits are not synced to installed paths"
|
||||
],
|
||||
"rollback": [
|
||||
"Revert the commit and copy prior script versions back into ~/.config/i3blocks and /usr/local/bin",
|
||||
"Restart i3 (i3-msg reload) and systemctl --user restart music-parallelism.service; verify process tree"
|
||||
]
|
||||
}
|
||||
@ -59,13 +59,15 @@ is_persist_mode() {
|
||||
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
|
||||
}
|
||||
|
||||
HEARTBEAT_INTERVAL_S=60
|
||||
|
||||
emit
|
||||
|
||||
if is_persist_mode; then
|
||||
# Intentionally calm heartbeat in persist mode: process-table event streams can
|
||||
# be extremely noisy and cause unnecessary churn.
|
||||
while true; do
|
||||
sleep 20
|
||||
sleep "$HEARTBEAT_INTERVAL_S"
|
||||
emit
|
||||
done
|
||||
fi
|
||||
|
||||
@ -17,6 +17,8 @@ is_persist_mode() {
|
||||
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
|
||||
}
|
||||
|
||||
WARP_POLL_INTERVAL_S=120
|
||||
|
||||
read_status() {
|
||||
local status line
|
||||
status=''
|
||||
@ -62,7 +64,7 @@ if is_persist_mode; then
|
||||
fi
|
||||
if is_persist_mode; then
|
||||
while true; do
|
||||
sleep 60
|
||||
sleep "$WARP_POLL_INTERVAL_S"
|
||||
current_status=$(read_status)
|
||||
emit_if_changed "$current_status"
|
||||
done
|
||||
|
||||
@ -80,26 +80,33 @@ MUSIC_SERVICES=(
|
||||
"pandora.com"
|
||||
)
|
||||
|
||||
build_regex_pattern() {
|
||||
local -n items=$1
|
||||
local pattern
|
||||
|
||||
printf -v pattern '%s|' "${items[@]}"
|
||||
printf '%s\n' "${pattern%|}"
|
||||
}
|
||||
|
||||
MUSIC_SERVICES_PATTERN=$(build_regex_pattern MUSIC_SERVICES)
|
||||
readonly MUSIC_SERVICES_PATTERN
|
||||
readonly MUSIC_WINDOWS_PATTERN='YouTube Music|music\.youtube\.com|music\.apple\.com|soundcloud\.com|pandora\.com|deezer\.com|tidal\.com'
|
||||
readonly ACTIVE_NO_MUSIC_INTERVAL=15
|
||||
readonly ACTIVE_AFTER_KILL_INTERVAL=5
|
||||
readonly IDLE_CHECK_INTERVAL=30
|
||||
|
||||
# Check if any music service is running and return its details (OPTIMIZED: batch pgrep calls)
|
||||
find_music_services() {
|
||||
local found_services=()
|
||||
|
||||
# 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 processes (single fork, no per-PID helpers)
|
||||
if pgrep -i -f "$music_pattern" &> /dev/null; then
|
||||
if pgrep -i -f "$MUSIC_SERVICES_PATTERN" &> /dev/null; then
|
||||
found_services+=("music process")
|
||||
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
|
||||
if xdotool search --name "$MUSIC_WINDOWS_PATTERN" &> /dev/null 2>&1; then
|
||||
found_services+=("music service (window)")
|
||||
fi
|
||||
fi
|
||||
@ -160,11 +167,12 @@ instant_monitor_loop() {
|
||||
local next_enforcement_ts=0
|
||||
local current_ts=0
|
||||
local focus_app=""
|
||||
local sleep_interval="$IDLE_CHECK_INTERVAL"
|
||||
|
||||
log_message "=== Music Parallelism INSTANT Monitor Started ==="
|
||||
log_message "Focus apps (windows): ${FOCUS_APPS_WINDOWS[*]}"
|
||||
log_message "Focus apps (processes): ${FOCUS_APPS_PROCESSES[*]}"
|
||||
log_message "Polling: ${FAST_CHECK_INTERVAL}s active, ${IDLE_CHECK_INTERVAL}s idle, ${ENFORCEMENT_COOLDOWN}s enforcement cooldown"
|
||||
log_message "Polling: ${FAST_CHECK_INTERVAL}s active, ${ACTIVE_NO_MUSIC_INTERVAL}s stable-focus, ${IDLE_CHECK_INTERVAL}s idle, ${ENFORCEMENT_COOLDOWN}s enforcement cooldown"
|
||||
|
||||
while true; do
|
||||
if focus_app=$(is_focus_app_running 2> /dev/null); then
|
||||
@ -174,15 +182,21 @@ instant_monitor_loop() {
|
||||
if kill_music_services; then
|
||||
notify_user "$focus_app"
|
||||
log_message "INSTANT KILL: Music services terminated"
|
||||
sleep_interval="$ACTIVE_AFTER_KILL_INTERVAL"
|
||||
fi
|
||||
else
|
||||
sleep_interval="$ACTIVE_NO_MUSIC_INTERVAL"
|
||||
fi
|
||||
next_enforcement_ts=$((current_ts + ENFORCEMENT_COOLDOWN))
|
||||
else
|
||||
sleep_interval="$ACTIVE_NO_MUSIC_INTERVAL"
|
||||
fi
|
||||
sleep "$FAST_CHECK_INTERVAL"
|
||||
else
|
||||
next_enforcement_ts=0
|
||||
sleep "$IDLE_CHECK_INTERVAL"
|
||||
sleep_interval="$IDLE_CHECK_INTERVAL"
|
||||
fi
|
||||
|
||||
sleep "$sleep_interval"
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@ -367,6 +367,22 @@ function has_noconfirm_flag() {
|
||||
return 1
|
||||
}
|
||||
|
||||
current_epoch() {
|
||||
printf '%(%s)T\n' -1
|
||||
}
|
||||
|
||||
current_day_of_week() {
|
||||
printf '%(%u)T\n' -1
|
||||
}
|
||||
|
||||
current_hour_24() {
|
||||
printf '%(%H)T\n' -1
|
||||
}
|
||||
|
||||
current_day_name() {
|
||||
printf '%(%A)T\n' -1
|
||||
}
|
||||
|
||||
# Helper: get list of PIDs holding a lock file (excluding our own PID)
|
||||
# Populates the $holders array
|
||||
get_lock_holders() {
|
||||
@ -453,7 +469,7 @@ check_and_handle_db_lock() {
|
||||
# Decide whether to remove the lock
|
||||
local now epoch age
|
||||
if epoch=$(stat -c %Y "$lock_file" 2>/dev/null); then
|
||||
now=$(date +%s)
|
||||
now=$(current_epoch)
|
||||
age=$((now - epoch))
|
||||
else
|
||||
age=999999
|
||||
@ -554,9 +570,9 @@ function check_for_steam() {
|
||||
# Function to check if current day is a weekday (after 4PM Friday until midnight Sunday)
|
||||
function is_weekday() {
|
||||
local day_of_week
|
||||
day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7)
|
||||
day_of_week=$(current_day_of_week) # %u gives 1-7 (Monday is 1, Sunday is 7)
|
||||
local hour
|
||||
hour=$(date +%H) # %H gives hour in 24-hour format (00-23)
|
||||
hour=$(current_hour_24) # %H gives hour in 24-hour format (00-23)
|
||||
|
||||
# Monday through Thursday are always weekdays
|
||||
if [[ $day_of_week -ge 1 && $day_of_week -le 4 ]]; then
|
||||
@ -647,9 +663,9 @@ function run_word_challenge() {
|
||||
# Timer display background process
|
||||
(
|
||||
local start_time current_time elapsed remaining
|
||||
start_time=$(date +%s)
|
||||
start_time=$(current_epoch)
|
||||
while true; do
|
||||
current_time=$(date +%s)
|
||||
current_time=$(current_epoch)
|
||||
elapsed=$((current_time - start_time))
|
||||
remaining=$((timeout_seconds - elapsed))
|
||||
if [[ $remaining -le 0 ]]; then
|
||||
@ -696,7 +712,7 @@ function prompt_for_steam_challenge() {
|
||||
# Check if it's a weekday and block completely
|
||||
if is_weekday; then
|
||||
local day_name
|
||||
day_name=$(date +%A)
|
||||
day_name=$(current_day_name)
|
||||
echo -e "${RED}Steam installation BLOCKED: Steam cannot be installed on weekdays.${NC}"
|
||||
echo -e "${RED}Today is $day_name. Please try again on the weekend (Saturday or Sunday).${NC}"
|
||||
return 1
|
||||
@ -773,7 +789,7 @@ display_operation "$1"
|
||||
echo -e "${GREEN}Executing:${NC} $PACMAN_BIN $*" >&2
|
||||
|
||||
# Record start time for statistics
|
||||
start_time=$(date +%s)
|
||||
start_time=$(current_epoch)
|
||||
|
||||
# Handle a possible stale DB lock before executing
|
||||
if ! check_and_handle_db_lock "$@"; then
|
||||
@ -796,7 +812,7 @@ if [[ $manual_hosts_guard -eq 1 ]]; then
|
||||
fi
|
||||
|
||||
# Record end time for statistics
|
||||
end_time=$(date +%s)
|
||||
end_time=$(current_epoch)
|
||||
duration=$((end_time - start_time))
|
||||
|
||||
# Display results
|
||||
|
||||
@ -13,8 +13,8 @@ source "$SCRIPT_DIR/../lib/common.sh"
|
||||
|
||||
# Schedule constants (single source of truth for this script)
|
||||
# These values are written to /etc/shutdown-schedule.conf during setup
|
||||
SCHEDULE_MON_WED_HOUR=24
|
||||
SCHEDULE_THU_SUN_HOUR=24
|
||||
SCHEDULE_MON_WED_HOUR=22
|
||||
SCHEDULE_THU_SUN_HOUR=22
|
||||
SCHEDULE_MORNING_END_HOUR=0
|
||||
|
||||
# ============================================================================
|
||||
@ -28,6 +28,54 @@ SCHEDULE_MORNING_END_HOUR=0
|
||||
|
||||
CANONICAL_CONFIG="/usr/local/share/locked-shutdown-schedule.conf"
|
||||
|
||||
# Validate that the schedule allows at least MIN_USAGE_HOURS of continuous PC usage.
|
||||
# The usable window is from SCHEDULE_MORNING_END_HOUR until each shutdown hour.
|
||||
# Both shutdown hours must independently satisfy the minimum (10 hours).
|
||||
MIN_USAGE_HOURS=10
|
||||
|
||||
validate_minimum_usage_window() {
|
||||
local mon_wed_window thu_sun_window
|
||||
mon_wed_window=$(( SCHEDULE_MON_WED_HOUR - SCHEDULE_MORNING_END_HOUR ))
|
||||
thu_sun_window=$(( SCHEDULE_THU_SUN_HOUR - SCHEDULE_MORNING_END_HOUR ))
|
||||
|
||||
local errors=()
|
||||
|
||||
if [[ $mon_wed_window -le 0 ]]; then
|
||||
errors+=("Mon-Wed: morning end (${SCHEDULE_MORNING_END_HOUR}:00) is at or after shutdown (${SCHEDULE_MON_WED_HOUR}:00) — 0 usable hours")
|
||||
elif [[ $mon_wed_window -lt $MIN_USAGE_HOURS ]]; then
|
||||
errors+=("Mon-Wed: only ${mon_wed_window}h of usable time (${SCHEDULE_MORNING_END_HOUR}:00–${SCHEDULE_MON_WED_HOUR}:00), need at least ${MIN_USAGE_HOURS}h")
|
||||
fi
|
||||
|
||||
if [[ $thu_sun_window -le 0 ]]; then
|
||||
errors+=("Thu-Sun: morning end (${SCHEDULE_MORNING_END_HOUR}:00) is at or after shutdown (${SCHEDULE_THU_SUN_HOUR}:00) — 0 usable hours")
|
||||
elif [[ $thu_sun_window -lt $MIN_USAGE_HOURS ]]; then
|
||||
errors+=("Thu-Sun: only ${thu_sun_window}h of usable time (${SCHEDULE_MORNING_END_HOUR}:00–${SCHEDULE_THU_SUN_HOUR}:00), need at least ${MIN_USAGE_HOURS}h")
|
||||
fi
|
||||
|
||||
if [[ ${#errors[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ❌ INVALID SCHEDULE CONFIGURATION ❌ ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "The schedule constants do not guarantee at least ${MIN_USAGE_HOURS} hours of"
|
||||
echo "continuous PC availability. This would cause the PC to shut down"
|
||||
echo "immediately or very shortly after it becomes usable."
|
||||
echo ""
|
||||
for err in "${errors[@]}"; do
|
||||
echo " ✗ $err"
|
||||
done
|
||||
echo ""
|
||||
echo "Fix: ensure (SHUTDOWN_HOUR - MORNING_END_HOUR) >= ${MIN_USAGE_HOURS} for both windows."
|
||||
echo " Example: MORNING_END_HOUR=6, SHUTDOWN_HOUR=22 → 16 usable hours ✓"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Validate schedule constants immediately (before any sudo escalation or file writes)
|
||||
validate_minimum_usage_window
|
||||
|
||||
# Check if trying to make schedule more lenient (later shutdown / earlier morning end)
|
||||
check_schedule_protection() {
|
||||
# Skip check if no canonical config exists (first install)
|
||||
@ -938,7 +986,9 @@ MONITOR_SERVICE="shutdown-timer-monitor.service"
|
||||
CHECK_INTERVAL=30
|
||||
|
||||
log_message() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2
|
||||
local _ts
|
||||
printf -v _ts '%(%Y-%m-%d %H:%M:%S)T' -1
|
||||
printf '%s [shutdown-monitor] %s\n' "$_ts" "$1" | tee -a "$LOG_FILE" >&2
|
||||
}
|
||||
|
||||
timer_needs_restoration() {
|
||||
@ -983,22 +1033,58 @@ restore_timer() {
|
||||
fi
|
||||
}
|
||||
|
||||
log_message "=== Shutdown Timer Monitor Started ==="
|
||||
log_message "Monitoring timer: $TIMER_NAME"
|
||||
monitor_with_dbus() {
|
||||
log_message "Starting shutdown timer monitoring with D-Bus events"
|
||||
|
||||
if timer_needs_restoration; then
|
||||
log_message "Initial check: Timer needs restoration"
|
||||
if command -v busctl &>/dev/null; then
|
||||
busctl monitor --system org.freedesktop.systemd1 2>/dev/null |
|
||||
while read -r line; do
|
||||
if [[ $line == *"$TIMER_NAME"* || $line == *"$SERVICE_NAME"* ]]; then
|
||||
log_message "Systemd event detected for shutdown timer"
|
||||
sleep 2
|
||||
if timer_needs_restoration; then
|
||||
restore_timer
|
||||
else
|
||||
log_message "Initial check: Timer is properly configured"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_message "busctl not available, falling back to polling"
|
||||
monitor_with_polling
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
monitor_with_polling() {
|
||||
log_message "Starting shutdown timer monitoring with polling (interval: ${CHECK_INTERVAL}s)"
|
||||
|
||||
while true; do
|
||||
if timer_needs_restoration; then
|
||||
restore_timer
|
||||
fi
|
||||
sleep "$CHECK_INTERVAL"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
start_monitoring() {
|
||||
log_message "=== Shutdown Timer Monitor Started ==="
|
||||
log_message "Monitoring timer: $TIMER_NAME"
|
||||
log_message "Monitoring service: $SERVICE_NAME"
|
||||
|
||||
if timer_needs_restoration; then
|
||||
log_message "Initial check: Timer needs restoration"
|
||||
restore_timer
|
||||
else
|
||||
log_message "Initial check: Timer is properly configured"
|
||||
fi
|
||||
|
||||
if command -v busctl &>/dev/null; then
|
||||
monitor_with_dbus
|
||||
else
|
||||
log_message "busctl not available, falling back to polling"
|
||||
monitor_with_polling
|
||||
fi
|
||||
}
|
||||
|
||||
start_monitoring
|
||||
EOF
|
||||
|
||||
chmod +x "$monitor_script"
|
||||
|
||||
@ -19,7 +19,7 @@ STATE_FILE="$STATE_DIR/work-time.state"
|
||||
LOCK_FILE="$STATE_DIR/tracker.lock"
|
||||
LOG_DIR="/var/log/thesis-work-tracker"
|
||||
LOG_FILE="$LOG_DIR/tracker.log"
|
||||
CHECK_INTERVAL=5 # Check every 5 seconds
|
||||
CHECK_INTERVAL=15 # Check every 15 seconds
|
||||
|
||||
# Work requirements (in seconds)
|
||||
# 2 hours of work = 7200 seconds required before Steam access
|
||||
@ -89,7 +89,7 @@ log_message() {
|
||||
shift
|
||||
local message="$*"
|
||||
local timestamp
|
||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
printf -v timestamp '%(%Y-%m-%d %H:%M:%S)T' -1
|
||||
echo "[${timestamp}] [${level}] ${message}" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
@ -117,13 +117,16 @@ init_state() {
|
||||
|
||||
# Initialize state file if it doesn't exist
|
||||
if [[ ! -f $STATE_FILE ]]; then
|
||||
local now_iso now_epoch
|
||||
printf -v now_iso '%(%Y-%m-%d %H:%M:%S)T' -1
|
||||
printf -v now_epoch '%(%s)T' -1
|
||||
cat <<EOF | sudo tee "$STATE_FILE" >/dev/null
|
||||
# Thesis Work Tracker State File
|
||||
# DO NOT EDIT MANUALLY - Managed by thesis_work_tracker daemon
|
||||
# Last updated: $(date)
|
||||
# Last updated: ${now_iso}
|
||||
|
||||
TOTAL_WORK_SECONDS=0
|
||||
LAST_UPDATE_TIMESTAMP=$(date +%s)
|
||||
LAST_UPDATE_TIMESTAMP=${now_epoch}
|
||||
STEAM_ACCESS_GRANTED=0
|
||||
LAST_WORK_SESSION_START=0
|
||||
CURRENT_SESSION_SECONDS=0
|
||||
@ -175,13 +178,16 @@ save_state() {
|
||||
sudo chattr -i "$STATE_FILE" 2>/dev/null || true
|
||||
|
||||
# Write new state
|
||||
local now_iso now_epoch
|
||||
printf -v now_iso '%(%Y-%m-%d %H:%M:%S)T' -1
|
||||
printf -v now_epoch '%(%s)T' -1
|
||||
cat <<EOF | sudo tee "$STATE_FILE" >/dev/null
|
||||
# Thesis Work Tracker State File
|
||||
# DO NOT EDIT MANUALLY - Managed by thesis_work_tracker daemon
|
||||
# Last updated: $(date)
|
||||
# Last updated: ${now_iso}
|
||||
|
||||
TOTAL_WORK_SECONDS=$total_work
|
||||
LAST_UPDATE_TIMESTAMP=$(date +%s)
|
||||
LAST_UPDATE_TIMESTAMP=${now_epoch}
|
||||
STEAM_ACCESS_GRANTED=$steam_access
|
||||
LAST_WORK_SESSION_START=$session_start
|
||||
CURRENT_SESSION_SECONDS=$current_session
|
||||
@ -215,7 +221,6 @@ get_active_window_info() {
|
||||
fi
|
||||
|
||||
local window_name
|
||||
window_name=$(xdotool getwindowname "$active_window_id" 2>/dev/null || echo "")
|
||||
|
||||
local window_pid
|
||||
window_pid=$(xdotool getwindowpid "$active_window_id" 2>/dev/null || echo "")
|
||||
@ -225,6 +230,11 @@ get_active_window_info() {
|
||||
process_name=$(ps -p "$window_pid" -o comm= 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
window_name=""
|
||||
if [[ $process_name == "Code" || $process_name == "code" ]]; then
|
||||
window_name=$(xdotool getwindowname "$active_window_id" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
echo "${process_name}|${window_name}"
|
||||
}
|
||||
|
||||
@ -379,13 +389,13 @@ main_loop() {
|
||||
fi
|
||||
|
||||
local last_status_log
|
||||
last_status_log=$(date +%s)
|
||||
printf -v last_status_log '%(%s)T' -1
|
||||
local last_decay_check
|
||||
last_decay_check=$(date +%s)
|
||||
printf -v last_decay_check '%(%s)T' -1
|
||||
|
||||
while true; do
|
||||
local current_time
|
||||
current_time=$(date +%s)
|
||||
printf -v current_time '%(%s)T' -1
|
||||
|
||||
# Check if thesis work is active
|
||||
if is_thesis_work_active; then
|
||||
@ -462,16 +472,18 @@ cleanup() {
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup SIGTERM SIGINT
|
||||
if [[ ${THESIS_WORK_TRACKER_SKIP_MAIN:-0} -ne 1 ]]; then
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
# Check for lock file to prevent multiple instances
|
||||
if [[ -f $LOCK_FILE ]]; then
|
||||
# Check for lock file to prevent multiple instances
|
||||
if [[ -f $LOCK_FILE ]]; then
|
||||
log_error "Another instance is already running (lock file exists: $LOCK_FILE)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create lock file
|
||||
touch "$LOCK_FILE"
|
||||
|
||||
# Run main loop
|
||||
main_loop
|
||||
fi
|
||||
|
||||
# Create lock file
|
||||
touch "$LOCK_FILE"
|
||||
|
||||
# Run main loop
|
||||
main_loop
|
||||
|
||||
@ -8,6 +8,7 @@ set -euo pipefail
|
||||
LOG_FILE="/var/log/hosts-file-monitor.log"
|
||||
HOSTS_FILE="/etc/hosts"
|
||||
HOSTS_INSTALL_SCRIPT="__HOSTS_INSTALL_SCRIPT__"
|
||||
readonly MIN_HOSTS_LINES=1000
|
||||
|
||||
# Log with timestamp (hosts-file-monitor specific)
|
||||
log_message() {
|
||||
@ -23,20 +24,39 @@ needs_restoration() {
|
||||
return 0 # File missing, needs restoration
|
||||
fi
|
||||
|
||||
# Check if file is empty or too small (less than 1000 lines indicates tampering)
|
||||
local line_count
|
||||
line_count=$(wc -l < "$HOSTS_FILE" 2> /dev/null || echo "0")
|
||||
if [[ $line_count -lt 1000 ]]; then
|
||||
# Check if file is empty or too small (less than MIN_HOSTS_LINES indicates tampering)
|
||||
local line_count=0
|
||||
local has_custom_entries=0
|
||||
local has_stevenblack_entries=0
|
||||
local line=""
|
||||
|
||||
while IFS= read -r line || [[ -n $line ]]; do
|
||||
line_count=$((line_count + 1))
|
||||
|
||||
if [[ $has_custom_entries -eq 0 && $line == *"Custom blocking entries"* ]]; then
|
||||
has_custom_entries=1
|
||||
fi
|
||||
|
||||
if [[ $has_stevenblack_entries -eq 0 && $line == *"StevenBlack"* ]]; then
|
||||
has_stevenblack_entries=1
|
||||
fi
|
||||
|
||||
if (( line_count >= MIN_HOSTS_LINES && has_custom_entries == 1 && has_stevenblack_entries == 1 )); then
|
||||
return 1 # File seems intact
|
||||
fi
|
||||
done < "$HOSTS_FILE"
|
||||
|
||||
if [[ $line_count -lt $MIN_HOSTS_LINES ]]; then
|
||||
return 0 # File too small, likely tampered with
|
||||
fi
|
||||
|
||||
# Check if our custom entries are missing
|
||||
if ! grep -q "Custom blocking entries" "$HOSTS_FILE" 2> /dev/null; then
|
||||
if [[ $has_custom_entries -eq 0 ]]; then
|
||||
return 0 # Our custom entries missing, needs restoration
|
||||
fi
|
||||
|
||||
# Check if StevenBlack entries are missing
|
||||
if ! grep -q "StevenBlack" "$HOSTS_FILE" 2> /dev/null; then
|
||||
if [[ $has_stevenblack_entries -eq 0 ]]; then
|
||||
return 0 # StevenBlack entries missing, needs restoration
|
||||
fi
|
||||
|
||||
@ -98,15 +118,20 @@ monitor_with_polling() {
|
||||
done
|
||||
}
|
||||
|
||||
# Main execution
|
||||
log_message "=== Hosts File Monitor Started ==="
|
||||
start_monitoring() {
|
||||
log_message "=== Hosts File Monitor Started ==="
|
||||
|
||||
# Check if inotify-tools is available
|
||||
if command -v inotifywait > /dev/null 2>&1; then
|
||||
if command -v inotifywait > /dev/null 2>&1; then
|
||||
log_message "Using inotify for file monitoring"
|
||||
monitor_with_inotify
|
||||
else
|
||||
else
|
||||
log_message "inotify-tools not available, using polling method"
|
||||
log_message "Consider installing inotify-tools for better performance: pacman -S inotify-tools"
|
||||
monitor_with_polling
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
if [[ ${HOSTS_FILE_MONITOR_SKIP_MAIN:-0} -ne 1 ]]; then
|
||||
start_monitoring
|
||||
fi
|
||||
|
||||
@ -160,21 +160,34 @@ current_day() {
|
||||
printf '%(%Y%m%d)T' -1
|
||||
}
|
||||
|
||||
seconds_until_next_day() {
|
||||
local hour minute second
|
||||
printf -v hour '%(%H)T' -1
|
||||
printf -v minute '%(%M)T' -1
|
||||
printf -v second '%(%S)T' -1
|
||||
printf '%s\n' $(((23 - 10#$hour) * 3600 + (59 - 10#$minute) * 60 + (60 - 10#$second)))
|
||||
}
|
||||
|
||||
while true; do
|
||||
day="$(current_day)"
|
||||
out_file="$LOG_DIR/pmon-${day}.log"
|
||||
rollover_pid=''
|
||||
|
||||
nvidia-smi pmon -d 10 -o DT >> "$out_file" 2>> "$ERR_LOG" &
|
||||
pmon_pid=$!
|
||||
|
||||
while kill -0 "$pmon_pid" >/dev/null 2>&1; do
|
||||
if [[ "$(current_day)" != "$day" ]]; then
|
||||
(
|
||||
sleep "$(seconds_until_next_day)"
|
||||
kill "$pmon_pid" >/dev/null 2>&1 || true
|
||||
) &
|
||||
rollover_pid=$!
|
||||
|
||||
wait "$pmon_pid" || true
|
||||
break
|
||||
|
||||
if [[ -n $rollover_pid ]]; then
|
||||
kill "$rollover_pid" >/dev/null 2>&1 || true
|
||||
wait "$rollover_pid" 2>/dev/null || true
|
||||
fi
|
||||
sleep 60
|
||||
done
|
||||
|
||||
done
|
||||
SCRIPT
|
||||
|
||||
@ -84,13 +84,13 @@ monitor_with_dbus() {
|
||||
log_message "Starting shutdown timer monitoring with D-Bus events"
|
||||
|
||||
# Use busctl to monitor systemd unit changes
|
||||
# Fall back to polling if this fails
|
||||
# Fall back to polling if this fails.
|
||||
if command -v busctl &>/dev/null; then
|
||||
# Monitor for unit state changes
|
||||
busctl monitor --system org.freedesktop.systemd1 2>/dev/null |
|
||||
while read -r line; do
|
||||
# Check if the line mentions our timer
|
||||
if echo "$line" | grep -q "$TIMER_NAME\|$SERVICE_NAME"; then
|
||||
if [[ $line == *"$TIMER_NAME"* || $line == *"$SERVICE_NAME"* ]]; then
|
||||
log_message "Systemd event detected for shutdown timer"
|
||||
sleep 2
|
||||
if timer_needs_restoration; then
|
||||
@ -116,18 +116,29 @@ monitor_with_polling() {
|
||||
done
|
||||
}
|
||||
|
||||
# Main execution
|
||||
log_message "=== Shutdown Timer Monitor Started ==="
|
||||
log_message "Monitoring timer: $TIMER_NAME"
|
||||
log_message "Monitoring service: $SERVICE_NAME"
|
||||
start_monitoring() {
|
||||
log_message "=== Shutdown Timer Monitor Started ==="
|
||||
log_message "Monitoring timer: $TIMER_NAME"
|
||||
log_message "Monitoring service: $SERVICE_NAME"
|
||||
|
||||
# Initial check
|
||||
if timer_needs_restoration; then
|
||||
# Initial check
|
||||
if timer_needs_restoration; then
|
||||
log_message "Initial check: Timer needs restoration"
|
||||
restore_timer
|
||||
else
|
||||
else
|
||||
log_message "Initial check: Timer is properly configured"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Use polling for reliability (D-Bus monitoring can miss events)
|
||||
monitor_with_polling
|
||||
# Prefer D-Bus monitoring, with polling as the fallback path.
|
||||
if command -v busctl &>/dev/null; then
|
||||
monitor_with_dbus
|
||||
else
|
||||
log_message "busctl not available, falling back to polling"
|
||||
monitor_with_polling
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
if [[ ${SHUTDOWN_TIMER_MONITOR_SKIP_MAIN:-0} -ne 1 ]]; then
|
||||
start_monitoring
|
||||
fi
|
||||
|
||||
@ -1,33 +1,42 @@
|
||||
#!/system/bin/sh
|
||||
# Runs early in boot - set up hosts file and start watchdog
|
||||
# MODDIR is set by Magisk and points to this module's directory
|
||||
GUARDIAN_DIR="/data/adb/android_guardian"
|
||||
# shellcheck disable=SC2034 # Used for documentation; heredoc defines its own
|
||||
MODULE_DIR="/data/adb/modules/android_guardian"
|
||||
WATCHDOG_SCRIPT="$GUARDIAN_DIR/watchdog.sh"
|
||||
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
|
||||
WATCHDOG_SCRIPT="${ANDROID_GUARDIAN_WATCHDOG_SCRIPT:-$GUARDIAN_DIR/watchdog.sh}"
|
||||
LOG_FILE="$GUARDIAN_DIR/guardian.log"
|
||||
|
||||
mkdir -p "$GUARDIAN_DIR"
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: $*" >>"$LOG_FILE"
|
||||
}
|
||||
|
||||
# Log that we're starting
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Guardian module loading" >>"$GUARDIAN_DIR/guardian.log"
|
||||
|
||||
# Create persistent watchdog script that runs independently of module state
|
||||
write_watchdog_script() {
|
||||
cat >"$WATCHDOG_SCRIPT" <<'WATCHDOG'
|
||||
#!/system/bin/sh
|
||||
# Secondary watchdog - runs independently of module state
|
||||
# Even if module is "disabled" in Magisk UI, this keeps running and undoes it
|
||||
GUARDIAN_DIR="/data/adb/android_guardian"
|
||||
MODULE_DIR="/data/adb/modules/android_guardian"
|
||||
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
|
||||
MODULE_DIR="${ANDROID_GUARDIAN_MODULE_DIR:-/data/adb/modules/android_guardian}"
|
||||
LOG_FILE="$GUARDIAN_DIR/watchdog.log"
|
||||
CONTROL_FILE="$GUARDIAN_DIR/control"
|
||||
HOSTS_BACKUP="$GUARDIAN_DIR/hosts.backup"
|
||||
MODULE_HOSTS="$MODULE_DIR/system/etc/hosts"
|
||||
LOOP_SLEEP_SECONDS=3
|
||||
HOSTS_CHECK_EVERY_TICKS=10
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
|
||||
}
|
||||
|
||||
log "=== Watchdog starting ==="
|
||||
is_enabled() {
|
||||
if [ ! -f "$CONTROL_FILE" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
while true; do
|
||||
# Protect module from Magisk UI disable/remove
|
||||
IFS= read -r guardian_state < "$CONTROL_FILE" || guardian_state=""
|
||||
[ "$guardian_state" = "ENABLED" ]
|
||||
}
|
||||
|
||||
protect_module_flags() {
|
||||
if [ -f "$MODULE_DIR/disable" ]; then
|
||||
log "ALERT: Module disable detected via Magisk UI - removing disable flag"
|
||||
rm -f "$MODULE_DIR/disable"
|
||||
@ -37,27 +46,56 @@ while true; do
|
||||
log "ALERT: Module removal detected via Magisk UI - removing remove flag"
|
||||
rm -f "$MODULE_DIR/remove"
|
||||
fi
|
||||
}
|
||||
|
||||
# Also protect the hosts file directly
|
||||
CONTROL_FILE="$GUARDIAN_DIR/control"
|
||||
if [ "$(cat "$CONTROL_FILE" 2>/dev/null)" = "ENABLED" ]; then
|
||||
if [ -f "$GUARDIAN_DIR/hosts.backup" ] && [ -f "$MODULE_DIR/system/etc/hosts" ]; then
|
||||
current_hash=$(md5sum "$MODULE_DIR/system/etc/hosts" 2>/dev/null | cut -d' ' -f1)
|
||||
backup_hash=$(md5sum "$GUARDIAN_DIR/hosts.backup" 2>/dev/null | cut -d' ' -f1)
|
||||
protect_hosts() {
|
||||
if [ ! -f "$HOSTS_BACKUP" ] || [ ! -f "$MODULE_HOSTS" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$current_hash" != "$backup_hash" ]; then
|
||||
if ! cmp -s "$MODULE_HOSTS" "$HOSTS_BACKUP"; then
|
||||
log "ALERT: Hosts tampering detected - restoring"
|
||||
cp "$GUARDIAN_DIR/hosts.backup" "$MODULE_DIR/system/etc/hosts"
|
||||
cp "$HOSTS_BACKUP" "$MODULE_HOSTS"
|
||||
fi
|
||||
}
|
||||
|
||||
log "=== Watchdog starting ==="
|
||||
|
||||
tick_count=0
|
||||
while true; do
|
||||
protect_module_flags
|
||||
|
||||
if is_enabled; then
|
||||
if [ $((tick_count % HOSTS_CHECK_EVERY_TICKS)) -eq 0 ]; then
|
||||
protect_hosts
|
||||
fi
|
||||
fi
|
||||
|
||||
sleep 3
|
||||
tick_count=$((tick_count + 1))
|
||||
sleep "$LOOP_SLEEP_SECONDS"
|
||||
done
|
||||
WATCHDOG
|
||||
}
|
||||
|
||||
chmod 755 "$WATCHDOG_SCRIPT"
|
||||
start_watchdog() {
|
||||
nohup sh "$WATCHDOG_SCRIPT" >/dev/null 2>&1 &
|
||||
}
|
||||
|
||||
# Start watchdog as a separate background process
|
||||
nohup sh "$WATCHDOG_SCRIPT" >/dev/null 2>&1 &
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: Watchdog started" >>"$GUARDIAN_DIR/guardian.log"
|
||||
post_fs_main() {
|
||||
mkdir -p "$GUARDIAN_DIR"
|
||||
log "Guardian module loading"
|
||||
write_watchdog_script
|
||||
chmod 755 "$WATCHDOG_SCRIPT"
|
||||
|
||||
if [ "${ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START:-0}" -ne 1 ]; then
|
||||
start_watchdog
|
||||
log "Watchdog started"
|
||||
return
|
||||
fi
|
||||
|
||||
log "Watchdog generation complete (start skipped)"
|
||||
}
|
||||
|
||||
if [ "${ANDROID_GUARDIAN_POST_FS_SKIP_MAIN:-0}" -ne 1 ]; then
|
||||
post_fs_main
|
||||
fi
|
||||
|
||||
@ -7,32 +7,39 @@
|
||||
# 4. Can only be stopped via ADB with the correct command
|
||||
|
||||
MODDIR=${0%/*}
|
||||
GUARDIAN_DIR="/data/adb/android_guardian"
|
||||
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
|
||||
LOG_FILE="$GUARDIAN_DIR/guardian.log"
|
||||
BLOCKED_APPS_FILE="$GUARDIAN_DIR/blocked_apps.txt"
|
||||
CONTROL_FILE="$GUARDIAN_DIR/control"
|
||||
HOSTS_BACKUP="$GUARDIAN_DIR/hosts.backup"
|
||||
MODULE_DIR="/data/adb/modules/android_guardian"
|
||||
MODULE_DIR="${ANDROID_GUARDIAN_MODULE_DIR:-/data/adb/modules/android_guardian}"
|
||||
SYSTEM_HOSTS_FILE="${ANDROID_GUARDIAN_SYSTEM_HOSTS_FILE:-/system/etc/hosts}"
|
||||
MODULE_HOSTS_FILE="${ANDROID_GUARDIAN_MODULE_HOSTS_FILE:-$MODDIR/system/etc/hosts}"
|
||||
DISABLE_FILE="$MODULE_DIR/disable"
|
||||
REMOVE_FILE="$MODULE_DIR/remove"
|
||||
|
||||
# Ensure guardian directory exists
|
||||
mkdir -p "$GUARDIAN_DIR"
|
||||
LOOP_SLEEP_SECONDS=5
|
||||
HOSTS_CHECK_EVERY_TICKS=6
|
||||
APPS_CHECK_EVERY_TICKS=12
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
|
||||
}
|
||||
|
||||
# Initialize control file if not exists
|
||||
[ ! -f "$CONTROL_FILE" ] && echo "ENABLED" >"$CONTROL_FILE"
|
||||
initialize_service() {
|
||||
mkdir -p "$GUARDIAN_DIR"
|
||||
|
||||
log "=== Android Guardian starting ==="
|
||||
if [ ! -f "$CONTROL_FILE" ]; then
|
||||
echo "ENABLED" >"$CONTROL_FILE"
|
||||
fi
|
||||
|
||||
# Enable wireless ADB on boot (persistent port 5555)
|
||||
setprop service.adb.tcp.port 5555
|
||||
stop adbd
|
||||
start adbd
|
||||
log "Wireless ADB enabled on port 5555"
|
||||
log "=== Android Guardian starting ==="
|
||||
|
||||
# Enable wireless ADB on boot (persistent port 5555)
|
||||
setprop service.adb.tcp.port 5555
|
||||
stop adbd
|
||||
start adbd
|
||||
log "Wireless ADB enabled on port 5555"
|
||||
}
|
||||
|
||||
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
|
||||
is_enabled() {
|
||||
@ -59,12 +66,9 @@ protect_module() {
|
||||
# Function to restore hosts file if tampered
|
||||
protect_hosts() {
|
||||
if [ -f "$HOSTS_BACKUP" ]; then
|
||||
current_hash=$(md5sum /system/etc/hosts 2>/dev/null | cut -d' ' -f1)
|
||||
backup_hash=$(md5sum "$HOSTS_BACKUP" 2>/dev/null | cut -d' ' -f1)
|
||||
|
||||
if [ "$current_hash" != "$backup_hash" ]; then
|
||||
if ! cmp -s "$SYSTEM_HOSTS_FILE" "$HOSTS_BACKUP"; then
|
||||
log "Hosts file tampering detected! Restoring..."
|
||||
cp "$HOSTS_BACKUP" "$MODDIR/system/etc/hosts"
|
||||
cp "$HOSTS_BACKUP" "$MODULE_HOSTS_FILE"
|
||||
log "Hosts file restored"
|
||||
fi
|
||||
fi
|
||||
@ -76,6 +80,14 @@ check_blocked_apps() {
|
||||
return
|
||||
fi
|
||||
|
||||
installed_packages=$(pm list packages 2>/dev/null) || installed_packages=""
|
||||
if [ -z "$installed_packages" ]; then
|
||||
return
|
||||
fi
|
||||
installed_packages="
|
||||
$installed_packages
|
||||
"
|
||||
|
||||
while IFS= read -r package || [ -n "$package" ]; do
|
||||
# Skip comments and empty lines
|
||||
case "$package" in
|
||||
@ -83,26 +95,45 @@ check_blocked_apps() {
|
||||
esac
|
||||
|
||||
# Check if package is installed
|
||||
if pm list packages 2>/dev/null | grep -q "package:$package"; then
|
||||
case "$installed_packages" in
|
||||
*"
|
||||
package:$package
|
||||
"*)
|
||||
log "Blocked app detected: $package - Uninstalling..."
|
||||
pm uninstall "$package" 2>/dev/null && log "Uninstalled: $package" || log "Failed to uninstall: $package"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done <"$BLOCKED_APPS_FILE"
|
||||
}
|
||||
|
||||
# Main monitoring loop - runs every 5 seconds for faster protection
|
||||
while true; do
|
||||
guardian_loop() {
|
||||
tick_count=0
|
||||
while true; do
|
||||
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
|
||||
# This ensures only ADB can control the guardian
|
||||
protect_module
|
||||
|
||||
if is_enabled; then
|
||||
if [ $((tick_count % HOSTS_CHECK_EVERY_TICKS)) -eq 0 ]; then
|
||||
protect_hosts
|
||||
check_blocked_apps
|
||||
fi
|
||||
|
||||
# Check every 5 seconds (faster response to disable attempts)
|
||||
sleep 5
|
||||
done &
|
||||
if [ $((tick_count % APPS_CHECK_EVERY_TICKS)) -eq 0 ]; then
|
||||
check_blocked_apps
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Guardian service started (PID: $!)"
|
||||
tick_count=$((tick_count + 1))
|
||||
sleep "$LOOP_SLEEP_SECONDS"
|
||||
done
|
||||
}
|
||||
|
||||
service_main() {
|
||||
initialize_service
|
||||
guardian_loop &
|
||||
log "Guardian service started (PID: $!)"
|
||||
}
|
||||
|
||||
if [ "${ANDROID_GUARDIAN_SKIP_MAIN:-0}" -ne 1 ]; then
|
||||
service_main
|
||||
fi
|
||||
|
||||
72
linux_configuration/tests/test_android_guardian_post_fs_data.sh
Executable file
72
linux_configuration/tests/test_android_guardian_post_fs_data.sh
Executable file
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for android_guardian post-fs-data watchdog generation.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/utils/android_guardian/post-fs-data.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
local file_path="$1"
|
||||
local pattern="$2"
|
||||
local context="$3"
|
||||
grep -q -- "$pattern" "$file_path" || fail "$context"
|
||||
}
|
||||
|
||||
assert_file_not_contains() {
|
||||
local file_path="$1"
|
||||
local pattern="$2"
|
||||
local context="$3"
|
||||
if grep -q -- "$pattern" "$file_path"; then
|
||||
fail "$context"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
GUARDIAN_DIR="$TMP_DIR/guardian"
|
||||
MODULE_DIR="$TMP_DIR/module"
|
||||
WATCHDOG_SCRIPT="$GUARDIAN_DIR/watchdog.sh"
|
||||
mkdir -p "$GUARDIAN_DIR" "$MODULE_DIR/system/etc"
|
||||
|
||||
printf 'Generating watchdog script in a temp Android guardian directory...\n'
|
||||
ANDROID_GUARDIAN_DIR="$GUARDIAN_DIR" \
|
||||
ANDROID_GUARDIAN_MODULE_DIR="$MODULE_DIR" \
|
||||
ANDROID_GUARDIAN_WATCHDOG_SCRIPT="$WATCHDOG_SCRIPT" \
|
||||
ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START=1 \
|
||||
sh "$TARGET_SCRIPT"
|
||||
|
||||
[[ -f "$WATCHDOG_SCRIPT" ]] || fail 'watchdog script should be generated'
|
||||
[[ -x "$WATCHDOG_SCRIPT" ]] || fail 'watchdog script should be executable'
|
||||
|
||||
printf 'Checking generated watchdog cadence and lower-fork host protection...\n'
|
||||
assert_file_contains "$WATCHDOG_SCRIPT" '^HOSTS_CHECK_EVERY_TICKS=10$' \
|
||||
'watchdog should throttle host protection to every 10 ticks'
|
||||
assert_file_contains "$WATCHDOG_SCRIPT" '^LOOP_SLEEP_SECONDS=3$' \
|
||||
'watchdog should keep the 3 second base sleep'
|
||||
assert_file_contains "$WATCHDOG_SCRIPT" 'cmp -s' \
|
||||
'watchdog should use cmp for host integrity checks'
|
||||
assert_file_not_contains "$WATCHDOG_SCRIPT" 'md5sum' \
|
||||
'watchdog should not use md5sum in the hot loop anymore'
|
||||
assert_file_not_contains "$WATCHDOG_SCRIPT" 'cut -d' \
|
||||
'watchdog should not pipe hashes through cut anymore'
|
||||
|
||||
printf 'Checking test hooks and generation log entry...\n'
|
||||
assert_file_contains "$TARGET_SCRIPT" 'ANDROID_GUARDIAN_POST_FS_SKIP_MAIN' \
|
||||
'post-fs-data should support skipping main for tests'
|
||||
assert_file_contains "$TARGET_SCRIPT" 'ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START' \
|
||||
'post-fs-data should support generating without starting the watchdog'
|
||||
assert_file_contains "$GUARDIAN_DIR/guardian.log" 'Watchdog generation complete (start skipped)' \
|
||||
'post-fs-data should log skipped watchdog starts during tests'
|
||||
|
||||
printf 'android_guardian post-fs-data regression checks passed.\n'
|
||||
69
linux_configuration/tests/test_android_guardian_service.sh
Executable file
69
linux_configuration/tests/test_android_guardian_service.sh
Executable file
@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for android_guardian service loop cadence.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/utils/android_guardian/service.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local context="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
fail "$context (expected: '$expected', actual: '$actual')"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
WORKTREE="$TMP_DIR/worktree"
|
||||
mkdir -p "$WORKTREE/scripts/utils/android_guardian"
|
||||
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/utils/android_guardian/service.sh"
|
||||
|
||||
printf 'Checking skip-main avoids boot side effects...\n'
|
||||
SKIP_MAIN_GUARDIAN_DIR="$TMP_DIR/skip-main-guardian"
|
||||
ANDROID_GUARDIAN_SKIP_MAIN=1 \
|
||||
ANDROID_GUARDIAN_DIR="$SKIP_MAIN_GUARDIAN_DIR" \
|
||||
ANDROID_GUARDIAN_MODULE_DIR="$TMP_DIR/skip-main-module" \
|
||||
sh "$TARGET_SCRIPT"
|
||||
|
||||
[[ ! -d "$SKIP_MAIN_GUARDIAN_DIR" ]] \
|
||||
|| fail 'skip-main should prevent guardian boot setup side effects'
|
||||
|
||||
printf 'Checking guardian loop cadence constants...\n'
|
||||
hosts_ticks=$(grep '^HOSTS_CHECK_EVERY_TICKS=' "$WORKTREE/scripts/utils/android_guardian/service.sh" | cut -d= -f2)
|
||||
apps_ticks=$(grep '^APPS_CHECK_EVERY_TICKS=' "$WORKTREE/scripts/utils/android_guardian/service.sh" | cut -d= -f2)
|
||||
sleep_seconds=$(grep '^LOOP_SLEEP_SECONDS=' "$WORKTREE/scripts/utils/android_guardian/service.sh" | cut -d= -f2)
|
||||
|
||||
assert_equals '6' "$hosts_ticks" 'hosts protection should run every 6 ticks'
|
||||
assert_equals '12' "$apps_ticks" 'blocked-app scan should run every 12 ticks'
|
||||
assert_equals '5' "$sleep_seconds" 'guardian loop should keep the 5 second base sleep'
|
||||
|
||||
printf 'Checking guardian loop protects module every tick...\n'
|
||||
grep -q 'protect_module' "$WORKTREE/scripts/utils/android_guardian/service.sh" \
|
||||
|| fail 'guardian loop must always protect the module each tick'
|
||||
|
||||
printf 'Checking blocked-app scans are cached per pass...\n'
|
||||
grep -q 'installed_packages=$(pm list packages 2>/dev/null)' "$WORKTREE/scripts/utils/android_guardian/service.sh" \
|
||||
|| fail 'guardian service should cache installed packages once per blocked-app scan'
|
||||
|
||||
if grep -q 'pm list packages 2>/dev/null | grep -q' "$WORKTREE/scripts/utils/android_guardian/service.sh"; then
|
||||
fail 'guardian service should not re-run pm list packages through grep for every blocked app'
|
||||
fi
|
||||
|
||||
printf 'Checking guardian loop uses skip-main guard for tests...\n'
|
||||
grep -q 'ANDROID_GUARDIAN_SKIP_MAIN' "$WORKTREE/scripts/utils/android_guardian/service.sh" \
|
||||
|| fail 'guardian service should support skip-main testing'
|
||||
|
||||
printf 'android_guardian service regression checks passed.\n'
|
||||
104
linux_configuration/tests/test_hosts_file_monitor.sh
Executable file
104
linux_configuration/tests/test_hosts_file_monitor.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for hosts-file-monitor.sh behavior.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/system-maintenance/bin/hosts-file-monitor.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local context="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
fail "$context (expected: '$expected', actual: '$actual')"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
WORKTREE="$TMP_DIR/worktree"
|
||||
BIN_DIR="$TMP_DIR/bin"
|
||||
mkdir -p "$WORKTREE/scripts/system-maintenance/bin" "$BIN_DIR"
|
||||
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/system-maintenance/bin/hosts-file-monitor.sh"
|
||||
|
||||
cat >"$BIN_DIR/tee" <<'EOF'
|
||||
#!/bin/bash
|
||||
while IFS= read -r _; do
|
||||
:
|
||||
done
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/tee"
|
||||
|
||||
cat >"$BIN_DIR/sleep" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$1" >> "${SLEEP_LOG:?}"
|
||||
exit 99
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/sleep"
|
||||
|
||||
cat >"$BIN_DIR/inotifywait" <<'EOF'
|
||||
#!/bin/bash
|
||||
while IFS= read -r line; do
|
||||
printf '%s\n' "$line"
|
||||
done <<< "${MOCK_INOTIFY_OUTPUT:-}"
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/inotifywait"
|
||||
|
||||
run_shell() {
|
||||
env -i PATH="$BIN_DIR" HOSTS_FILE_MONITOR_SKIP_MAIN=1 /bin/bash -c "$1"
|
||||
}
|
||||
|
||||
make_hosts_file() {
|
||||
local file_path="$1"
|
||||
local include_custom="$2"
|
||||
local include_stevenblack="$3"
|
||||
local i
|
||||
|
||||
: >"$file_path"
|
||||
for ((i = 1; i <= 1005; i++)); do
|
||||
printf '127.0.0.1 example-%d.test\n' "$i" >>"$file_path"
|
||||
done
|
||||
|
||||
if [[ $include_custom -eq 1 ]]; then
|
||||
printf '# Custom blocking entries\n' >>"$file_path"
|
||||
fi
|
||||
|
||||
if [[ $include_stevenblack -eq 1 ]]; then
|
||||
printf '# StevenBlack hosts\n' >>"$file_path"
|
||||
fi
|
||||
}
|
||||
|
||||
printf 'Checking intact hosts files are accepted...\n'
|
||||
hosts_ok="$TMP_DIR/hosts-ok"
|
||||
make_hosts_file "$hosts_ok" 1 1
|
||||
ok_result=$(run_shell "source '$WORKTREE/scripts/system-maintenance/bin/hosts-file-monitor.sh'; HOSTS_FILE='$hosts_ok'; if needs_restoration; then printf restore; else printf ok; fi")
|
||||
assert_equals 'ok' "$ok_result" 'needs_restoration should accept intact hosts files'
|
||||
|
||||
printf 'Checking missing markers trigger restoration...\n'
|
||||
hosts_missing="$TMP_DIR/hosts-missing"
|
||||
make_hosts_file "$hosts_missing" 1 0
|
||||
missing_result=$(run_shell "source '$WORKTREE/scripts/system-maintenance/bin/hosts-file-monitor.sh'; HOSTS_FILE='$hosts_missing'; if needs_restoration; then printf restore; else printf ok; fi")
|
||||
assert_equals 'restore' "$missing_result" 'needs_restoration should reject files missing required markers'
|
||||
|
||||
printf 'Checking inotify path is preferred when available...\n'
|
||||
inotify_mode=$(run_shell "source '$WORKTREE/scripts/system-maintenance/bin/hosts-file-monitor.sh'; monitor_with_inotify() { printf inotify; }; monitor_with_polling() { printf polling; }; start_monitoring")
|
||||
assert_equals 'inotify' "$inotify_mode" 'start_monitoring should prefer inotifywait when present'
|
||||
|
||||
printf 'Checking polling fallback is used without inotifywait...\n'
|
||||
mv "$BIN_DIR/inotifywait" "$BIN_DIR/inotifywait.off"
|
||||
poll_mode=$(run_shell "source '$WORKTREE/scripts/system-maintenance/bin/hosts-file-monitor.sh'; monitor_with_inotify() { printf inotify; }; monitor_with_polling() { printf polling; }; start_monitoring")
|
||||
mv "$BIN_DIR/inotifywait.off" "$BIN_DIR/inotifywait"
|
||||
assert_equals 'polling' "$poll_mode" 'start_monitoring should fall back to polling when inotifywait is absent'
|
||||
|
||||
printf 'hosts-file-monitor.sh regression checks passed.\n'
|
||||
@ -193,6 +193,8 @@ printf 'Checking focus detection path avoids extra xdotool lookups...\n'
|
||||
printf 'Checking ActivityWatch persist strategy avoids /proc event storm...\n'
|
||||
! grep -Fq 'inotifywait -m -q -e create -e delete /proc' "$I3BLOCKS_DIR/activitywatch_status.sh" \
|
||||
|| fail 'activitywatch persist mode should avoid noisy /proc inotify stream'
|
||||
grep -Fq 'HEARTBEAT_INTERVAL_S=60' "$I3BLOCKS_DIR/activitywatch_status.sh" \
|
||||
|| fail 'activitywatch persist mode should use a 60 second calm heartbeat'
|
||||
|
||||
printf 'Checking GPU/WARP dedupe guards exist...\n'
|
||||
grep -Fq 'emit_if_changed()' "$I3BLOCKS_DIR/gpu_monitor.sh" \
|
||||
@ -219,6 +221,8 @@ grep -Fq 'i3blocks_update_if_changed_key "ethernet_output"' "$I3BLOCKS_DIR/ether
|
||||
|| fail 'ethernet script should dedupe unchanged output'
|
||||
grep -Fq 'i3blocks_update_if_changed_key "activitywatch_state"' "$I3BLOCKS_DIR/activitywatch_status.sh" \
|
||||
|| fail 'activitywatch script should dedupe unchanged state'
|
||||
grep -Fq 'WARP_POLL_INTERVAL_S=120' "$I3BLOCKS_DIR/warp_status.sh" \
|
||||
|| fail 'warp status should poll at a calmer 120 second interval'
|
||||
|
||||
printf 'Checking bluetooth block behavior and fork count...\n'
|
||||
bluetooth_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/bluetooth.sh")
|
||||
|
||||
108
linux_configuration/tests/test_music_parallelism.sh
Executable file
108
linux_configuration/tests/test_music_parallelism.sh
Executable file
@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for the music parallelism daemon's polling cadence.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/digital_wellbeing/music_parallelism.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local context="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
fail "$context (expected: '$expected', actual: '$actual')"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
WORKTREE="$TMP_DIR/worktree"
|
||||
BIN_DIR="$TMP_DIR/bin"
|
||||
mkdir -p "$WORKTREE/scripts/digital_wellbeing" "$WORKTREE/scripts/lib" "$BIN_DIR"
|
||||
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/digital_wellbeing/music_parallelism.sh"
|
||||
|
||||
cat >"$WORKTREE/scripts/lib/common.sh" <<'EOF'
|
||||
#!/bin/bash
|
||||
|
||||
FOCUS_APPS_WINDOWS=("Mock Focus App")
|
||||
FOCUS_APPS_PROCESSES=("mock-focus-proc")
|
||||
|
||||
is_focus_app_running() {
|
||||
if [[ ${MOCK_FOCUS_ACTIVE:-0} -eq 1 ]]; then
|
||||
printf '%s\n' "Mock Focus App"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
get_timestamp() {
|
||||
printf '%s\n' "${MOCK_TIMESTAMP:-1000}"
|
||||
}
|
||||
|
||||
log_message() {
|
||||
:
|
||||
}
|
||||
EOF
|
||||
chmod +x "$WORKTREE/scripts/lib/common.sh"
|
||||
|
||||
cat >"$BIN_DIR/pgrep" <<'EOF'
|
||||
#!/bin/bash
|
||||
if [[ ${MOCK_MUSIC_RUNNING:-0} -eq 1 ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/pgrep"
|
||||
|
||||
cat >"$BIN_DIR/pkill" <<'EOF'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/pkill"
|
||||
|
||||
cat >"$BIN_DIR/sleep" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$1" >> "${SLEEP_LOG:?}"
|
||||
exit 99
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/sleep"
|
||||
|
||||
run_case() {
|
||||
local expected_sleep="$1"
|
||||
local focus_active="$2"
|
||||
local music_running="$3"
|
||||
local sleep_log="$TMP_DIR/sleep.log"
|
||||
|
||||
: >"$sleep_log"
|
||||
PATH="$BIN_DIR:$PATH" \
|
||||
SLEEP_LOG="$sleep_log" \
|
||||
MOCK_FOCUS_ACTIVE="$focus_active" \
|
||||
MOCK_MUSIC_RUNNING="$music_running" \
|
||||
bash "$WORKTREE/scripts/digital_wellbeing/music_parallelism.sh" instant \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
assert_equals "$expected_sleep" "$(<"$sleep_log")" "music_parallelism.sh should pick the expected sleep interval"
|
||||
}
|
||||
|
||||
printf 'Checking stable-focus backoff uses the slower interval...\n'
|
||||
run_case 15 1 0
|
||||
|
||||
printf 'Checking conflict handling uses the faster retry interval...\n'
|
||||
run_case 5 1 1
|
||||
|
||||
printf 'Checking idle mode uses the idle interval...\n'
|
||||
run_case 30 0 0
|
||||
|
||||
printf 'music_parallelism.sh regression checks passed.\n'
|
||||
@ -182,6 +182,27 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 20: Verify wrapper uses builtin time helpers instead of external date
|
||||
echo "[TEST 20] Verifying wrapper uses builtin time helpers..."
|
||||
if grep -q "current_epoch()" "$WRAPPER_DIR/pacman_wrapper.sh" \
|
||||
&& grep -q "current_day_of_week()" "$WRAPPER_DIR/pacman_wrapper.sh" \
|
||||
&& grep -q "current_hour_24()" "$WRAPPER_DIR/pacman_wrapper.sh" \
|
||||
&& grep -q "current_day_name()" "$WRAPPER_DIR/pacman_wrapper.sh"; then
|
||||
echo "✓ Builtin time helper functions found"
|
||||
else
|
||||
echo "✗ Builtin time helper functions missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 21: Verify wrapper avoids external date calls in hot paths
|
||||
echo "[TEST 21] Verifying wrapper avoids external date calls in hot paths..."
|
||||
if ! grep -q "date +%s\|date +%u\|date +%H\|date +%A" "$WRAPPER_DIR/pacman_wrapper.sh"; then
|
||||
echo "✓ External date calls removed from hot paths"
|
||||
else
|
||||
echo "✗ External date calls still present in hot paths"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== All Tests Passed! ==="
|
||||
echo ""
|
||||
@ -195,3 +216,4 @@ echo " ✓ Difficult word challenge for VirtualBox installation (7-letter words
|
||||
echo " ✓ makepkg capped runner is integrated via wrapper and installer"
|
||||
echo " ✓ mkpkg convenience helper is deployed by installer"
|
||||
echo " ✓ installer fails fast and handles immutable policy files safely"
|
||||
echo " ✓ pacman wrapper hot paths use bash builtin time helpers"
|
||||
|
||||
123
linux_configuration/tests/test_shutdown_timer_monitor.sh
Executable file
123
linux_configuration/tests/test_shutdown_timer_monitor.sh
Executable file
@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for shutdown-timer-monitor.sh dispatcher behavior.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/system-maintenance/bin/shutdown-timer-monitor.sh"
|
||||
SETUP_SCRIPT="$REPO_DIR/scripts/digital_wellbeing/setup_midnight_shutdown.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local context="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
fail "$context (expected: '$expected', actual: '$actual')"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
WORKTREE="$TMP_DIR/worktree"
|
||||
BIN_DIR="$TMP_DIR/bin"
|
||||
mkdir -p "$WORKTREE/scripts/system-maintenance/bin" "$BIN_DIR"
|
||||
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/system-maintenance/bin/shutdown-timer-monitor.sh"
|
||||
|
||||
cat >"$BIN_DIR/busctl" <<'EOF'
|
||||
#!/bin/bash
|
||||
if [[ $1 == monitor ]]; then
|
||||
printf '%s\n' "${MOCK_BUSCTL_LINE:-no relevant event}" | while read -r line; do
|
||||
printf '%s\n' "$line"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
printf 'unexpected busctl args: %s\n' "$*" >&2
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/busctl"
|
||||
|
||||
cat >"$BIN_DIR/systemctl" <<'EOF'
|
||||
#!/bin/bash
|
||||
case "$*" in
|
||||
'is-enabled day-specific-shutdown.timer'|'is-active day-specific-shutdown.timer')
|
||||
exit 0
|
||||
;;
|
||||
'daemon-reload'|'enable day-specific-shutdown.timer'|'start day-specific-shutdown.timer')
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf 'unexpected systemctl args: %s\n' "$*" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/systemctl"
|
||||
|
||||
cat >"$BIN_DIR/sleep" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$1" >> "${SLEEP_LOG:?}"
|
||||
exit 99
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/sleep"
|
||||
|
||||
cat >"$BIN_DIR/tee" <<'EOF'
|
||||
#!/bin/bash
|
||||
while IFS= read -r _; do
|
||||
:
|
||||
done
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/tee"
|
||||
|
||||
run_case() {
|
||||
local expected_mode="$1"
|
||||
local busctl_present="$2"
|
||||
local sleep_log="$TMP_DIR/sleep.log"
|
||||
local mode_file="$TMP_DIR/mode.log"
|
||||
|
||||
: >"$sleep_log"
|
||||
: >"$mode_file"
|
||||
if [[ $busctl_present -eq 0 ]]; then
|
||||
mv "$BIN_DIR/busctl" "$BIN_DIR/busctl.off"
|
||||
fi
|
||||
|
||||
mode=$(env -i PATH="$BIN_DIR" SLEEP_LOG="$sleep_log" SHUTDOWN_TIMER_MONITOR_SKIP_MAIN=1 /bin/bash -c \
|
||||
"source '$WORKTREE/scripts/system-maintenance/bin/shutdown-timer-monitor.sh'; \
|
||||
timer_needs_restoration() { return 1; }; \
|
||||
restore_timer() { :; }; \
|
||||
monitor_with_dbus() { printf 'dbus'; }; \
|
||||
monitor_with_polling() { printf 'polling'; }; \
|
||||
start_monitoring")
|
||||
|
||||
assert_equals "$expected_mode" "$mode" 'shutdown timer monitor should choose the expected dispatcher'
|
||||
|
||||
if [[ -f "$BIN_DIR/busctl.off" ]]; then
|
||||
mv "$BIN_DIR/busctl.off" "$BIN_DIR/busctl"
|
||||
fi
|
||||
}
|
||||
|
||||
printf 'Checking D-Bus path is preferred when busctl exists...\n'
|
||||
run_case dbus 1
|
||||
|
||||
printf 'Checking polling fallback is used when busctl is absent...\n'
|
||||
run_case polling 0
|
||||
|
||||
printf 'Checking installer template stays in sync with the event-driven monitor...\n'
|
||||
grep -Fq 'monitor_with_dbus()' "$SETUP_SCRIPT" \
|
||||
|| fail 'setup_midnight_shutdown.sh should install the D-Bus monitor helper'
|
||||
grep -Fq 'start_monitoring()' "$SETUP_SCRIPT" \
|
||||
|| fail 'setup_midnight_shutdown.sh should install the start_monitoring dispatcher'
|
||||
grep -Fq 'if command -v busctl &>/dev/null; then' "$SETUP_SCRIPT" \
|
||||
|| fail 'setup_midnight_shutdown.sh should prefer busctl when available'
|
||||
|
||||
printf 'shutdown-timer-monitor.sh regression checks passed.\n'
|
||||
91
linux_configuration/tests/test_thesis_work_tracker.sh
Executable file
91
linux_configuration/tests/test_thesis_work_tracker.sh
Executable file
@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
# Regression tests for thesis_work_tracker.sh helper behavior.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
||||
TARGET_SCRIPT="$REPO_DIR/scripts/digital_wellbeing/thesis_work_tracker.sh"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local expected="$1"
|
||||
local actual="$2"
|
||||
local context="$3"
|
||||
if [[ "$expected" != "$actual" ]]; then
|
||||
fail "$context (expected: '$expected', actual: '$actual')"
|
||||
fi
|
||||
}
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
WORKTREE="$TMP_DIR/worktree"
|
||||
BIN_DIR="$TMP_DIR/bin"
|
||||
mkdir -p "$WORKTREE/scripts/digital_wellbeing" "$BIN_DIR"
|
||||
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/digital_wellbeing/thesis_work_tracker.sh"
|
||||
|
||||
cat >"$BIN_DIR/xdotool" <<'EOF'
|
||||
#!/bin/bash
|
||||
case "$*" in
|
||||
'getactivewindow')
|
||||
printf '12345\n'
|
||||
;;
|
||||
'getwindowpid 12345')
|
||||
printf '6789\n'
|
||||
;;
|
||||
'getwindowname 12345')
|
||||
printf '%s\n' "${MOCK_WINDOW_TITLE:-Document - praca_magisterska - Visual Studio Code}"
|
||||
;;
|
||||
*)
|
||||
printf 'unexpected xdotool args: %s\n' "$*" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/xdotool"
|
||||
|
||||
cat >"$BIN_DIR/ps" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "${MOCK_PROCESS_NAME:-Code}"
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/ps"
|
||||
|
||||
cat >"$BIN_DIR/pgrep" <<'EOF'
|
||||
#!/bin/bash
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/pgrep"
|
||||
|
||||
cat >"$BIN_DIR/date" <<'EOF'
|
||||
#!/bin/bash
|
||||
printf 'date should not be called\n' >&2
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x "$BIN_DIR/date"
|
||||
|
||||
source_env() {
|
||||
PATH="$BIN_DIR:$PATH" THESIS_WORK_TRACKER_SKIP_MAIN=1 bash -lc "source '$WORKTREE/scripts/digital_wellbeing/thesis_work_tracker.sh'; $1"
|
||||
}
|
||||
|
||||
printf 'Checking helper output for VS Code on thesis repo...\n'
|
||||
result=$(source_env 'get_active_window_info')
|
||||
assert_equals 'Code|Document - praca_magisterska - Visual Studio Code' "$result" \
|
||||
'get_active_window_info should return process and title for VS Code'
|
||||
|
||||
printf 'Checking thesis detection for VS Code thesis repo...\n'
|
||||
active=$(source_env 'is_thesis_work_active && printf yes || printf no')
|
||||
assert_equals 'yes' "$active" 'thesis detection should accept VS Code on the thesis repo'
|
||||
|
||||
printf 'Checking thesis detection skips non-thesis VS Code windows...\n'
|
||||
non_thesis=$(MOCK_WINDOW_TITLE='Document - notes - Visual Studio Code' source_env 'is_thesis_work_active && printf yes || printf no')
|
||||
assert_equals 'no' "$non_thesis" 'thesis detection should reject VS Code outside the thesis repo'
|
||||
|
||||
printf 'thesis_work_tracker.sh regression checks passed.\n'
|
||||
@ -26,9 +26,21 @@ printf 'Checking pmon logger template avoids read -t busy-loop pattern...\n'
|
||||
! grep -q 'read -r -t' <<< "$logger_template" \
|
||||
|| fail 'logger template must not use read -t as sleep surrogate'
|
||||
|
||||
printf 'Checking pmon logger template uses sleep-based waiting...\n'
|
||||
grep -q 'sleep 60' <<< "$logger_template" \
|
||||
|| fail 'logger template must sleep between day rollover checks'
|
||||
printf 'Checking pmon logger template uses a sleep-until-midnight helper...\n'
|
||||
grep -q 'seconds_until_next_day()' <<< "$logger_template" \
|
||||
|| fail 'logger template must define seconds_until_next_day for rollover timing'
|
||||
|
||||
printf 'Checking pmon logger template avoids minute polling loop...\n'
|
||||
! grep -q 'sleep 60' <<< "$logger_template" \
|
||||
|| fail 'logger template must not poll every minute for day rollover'
|
||||
|
||||
printf 'Checking pmon logger template avoids repeated kill -0 probes...\n'
|
||||
! grep -q 'while kill -0' <<< "$logger_template" \
|
||||
|| fail 'logger template must not spin on kill -0 for day rollover detection'
|
||||
|
||||
printf 'Checking pmon logger template starts a rollover sleeper...\n'
|
||||
grep -q 'sleep "\$(seconds_until_next_day)"' <<< "$logger_template" \
|
||||
|| fail 'logger template must sleep until midnight before rotating pmon'
|
||||
|
||||
printf 'Checking pmon logger template uses fork-free date builtin...\n'
|
||||
grep -q "printf '%(%Y%m%d)T' -1" <<< "$logger_template" \
|
||||
|
||||
@ -275,6 +275,7 @@ com.microsoft.office.outlook
|
||||
com.google.android.gm
|
||||
ch.protonmail.android
|
||||
com.microsoft.teams
|
||||
com.facebook.katana
|
||||
com.facebook.orca
|
||||
|
||||
# --- App installation alternatives (must stay usable in focus mode) ---
|
||||
|
||||
@ -86,6 +86,10 @@ PROTECTED_APP_IDS = {
|
||||
220200,
|
||||
3527290, # Peak
|
||||
1331550,
|
||||
8930,
|
||||
1158310,
|
||||
440,
|
||||
1142710,
|
||||
}
|
||||
|
||||
STEAMAPPS_PATH = Path("~/.local/share/Steam/steamapps").expanduser()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user