Harden runtime script deployment and enforce installer safety

This commit is contained in:
Krzysztof kuhy Rudnicki 2026-05-08 17:44:22 +02:00
parent 1c90577b40
commit 1ebb667265
29 changed files with 1150 additions and 241 deletions

View File

@ -0,0 +1,15 @@
{
"title": "Linux config runtime hardening and deployment contract",
"objective": "Deploy and verify improved Linux scripts in runtime (not just repository state), while ensuring pacman wrapper installation handles immutable files safely and fails fast on required-file errors.",
"acceptance_criteria": [
"Pacman wrapper exposes `--makepkg-capped` and `/usr/local/bin/makepkg_capped` + `/usr/local/bin/mkpkg` are deployed.",
"Hardened installer runs successfully and no longer reports partial-success permission failures for policy/integrity writes.",
"Optimized i3blocks scripts/config are present in ~/.config/i3blocks and active processes are running those scripts.",
"Pre-commit passes for all modified files involved in this change set."
],
"out_of_scope": [
"Repository-wide cleanup of unrelated legacy pre-commit failures in untouched files.",
"Functional redesign of steam_backlog_enforcer logic beyond included pending edits."
],
"verifier": "pre-commit run --files <all modified files>; bash linux_configuration/tests/test_pacman_wrapper_security.sh; runtime probes for deployed binaries/processes"
}

View File

@ -0,0 +1,41 @@
{
"intent": "Ensure improved Linux configuration scripts are actually deployed/running and harden pacman wrapper installation against partial-success permission failures.",
"scope": [
"linux_configuration/i3-configuration/i3blocks/*",
"linux_configuration/scripts/digital_wellbeing/pacman/*",
"linux_configuration/scripts/system-maintenance/bin/usage_report.py",
"linux_configuration/tests/*",
"python_pkg/steam_backlog_enforcer/*"
],
"changes": [
"Added and integrated constrained makepkg execution (`makepkg_capped`, `mkpkg`, wrapper command routing) and installer deployment paths.",
"Hardened pacman installer with strict mode and immutable-file unlock/relock flow to avoid partial-success installs.",
"Improved i3blocks runtime deployment by syncing optimized scripts/config and confirming active processes use persist helper logic.",
"Added/updated regression tests for pacman wrapper security, i3blocks persist helper, usage monitoring installer, and pmon process-name normalization."
],
"verification": [
{
"command": "pre-commit run --files linux_configuration/i3-configuration/i3blocks/activitywatch_status.sh linux_configuration/i3-configuration/i3blocks/bluetooth.sh linux_configuration/i3-configuration/i3blocks/config linux_configuration/i3-configuration/i3blocks/ethernet.sh linux_configuration/i3-configuration/i3blocks/gpu_monitor.sh linux_configuration/i3-configuration/i3blocks/warp_status.sh linux_configuration/i3-configuration/i3blocks/wifi_monitor.sh linux_configuration/scripts/digital_wellbeing/music_parallelism.sh linux_configuration/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh linux_configuration/scripts/digital_wellbeing/pacman/pacman_wrapper.sh linux_configuration/scripts/lib/common.sh linux_configuration/scripts/system-maintenance/bin/install_usage_monitoring.sh linux_configuration/scripts/system-maintenance/bin/usage_report.py linux_configuration/tests/test_i3blocks_efficiency.sh linux_configuration/tests/test_pacman_wrapper_security.sh linux_configuration/tests/test_i3blocks_persist_common.sh python_pkg/steam_backlog_enforcer/enforcer.py python_pkg/steam_backlog_enforcer/game_install.py python_pkg/steam_backlog_enforcer/steam-backlog-enforcer.service python_pkg/steam_backlog_enforcer/tests/test_enforcer.py linux_configuration/i3-configuration/i3blocks/persist_common.sh linux_configuration/scripts/digital_wellbeing/pacman/makepkg_capped.sh linux_configuration/scripts/digital_wellbeing/pacman/mkpkg.sh linux_configuration/tests/__init__.py linux_configuration/tests/test_makepkg_capped.sh linux_configuration/tests/test_usage_monitoring_installer_efficiency.sh linux_configuration/tests/test_usage_report_pmon_names.py",
"result": "pass",
"evidence": "All hooks passed including no-polling-antipatterns, ruff, shellcheck, and leak checks."
},
{
"command": "bash linux_configuration/tests/test_pacman_wrapper_security.sh",
"result": "pass",
"evidence": "All 19 security/integration checks passed, including strict installer mode and immutable-file handling markers."
},
{
"command": "bash linux_configuration/scripts/digital_wellbeing/pacman/install_pacman_wrapper.sh",
"result": "pass",
"evidence": "Installer completed without prior Operation not permitted failures; deployed binaries and wrapper help verified."
}
],
"risks": [
"Installer now fails fast for missing required source files, which may stop previously permissive installs.",
"Live i3blocks config/script sync may diverge from user-local manual tweaks if reapplied blindly."
],
"rollback": [
"Revert commit and rerun installer to restore previous wrapper behavior.",
"Restore i3blocks config from generated backup in ~/.config/i3blocks/config.bak.<timestamp> and restart i3blocks."
]
}

View File

@ -3,6 +3,11 @@
set -euo pipefail
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
check_installed() {
command -v aw-qt > /dev/null 2>&1 || command -v aw-server > /dev/null 2>&1
}
@ -21,16 +26,46 @@ check_running() {
return 1
}
if ! check_installed; then
emit() {
local state
if ! check_installed; then
state='uninstalled'
elif check_running; then
state='on'
else
state='off'
fi
if ! i3blocks_update_if_changed_key "activitywatch_state" "$state"; then
return 0
fi
if [[ $state == 'uninstalled' ]]; then
echo "AW uninstalled"
echo
echo "#FF0000"
elif check_running; then
elif [[ $state == 'on' ]]; then
echo "AW on"
echo
echo "#00FF00"
else
else
echo "AW off"
echo
echo "#FF0000"
fi
}
is_persist_mode() {
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
}
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
emit
done
fi

View File

@ -3,11 +3,24 @@
set -euo pipefail
bluetooth_info=$(bluetoothctl info 2> /dev/null) || bluetooth_info=''
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
connected='no'
device=''
while IFS= read -r line; do
get_bluetooth_info() {
local info
info=$(bluetoothctl info 2> /dev/null) || info=''
printf '%s\n' "$info"
}
emit() {
local bluetooth_info connected device line state_key
bluetooth_info=$(get_bluetooth_info)
connected='no'
device=''
while IFS= read -r line; do
case $line in
*'Connected: yes')
connected='yes'
@ -16,12 +29,44 @@ while IFS= read -r line; do
device=${line#*Alias: }
;;
esac
done <<< "$bluetooth_info"
done <<< "$bluetooth_info"
if [[ $connected == yes && -n $device ]]; then
state_key="$connected|$device"
if ! i3blocks_update_if_changed_key "bluetooth_state" "$state_key"; then
return 0
fi
if [[ $connected == yes && -n $device ]]; then
echo "$device"
echo
echo "#50FA7B"
else
else
echo " Disconnected"
fi
}
is_persist_mode() {
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
}
emit_throttled() {
if ! i3blocks_should_emit_by_interval_key "bluetooth_emit" "$EMIT_MIN_INTERVAL_S"; then
return 0
fi
emit
}
EMIT_MIN_INTERVAL_S=2
emit
if is_persist_mode; then
# React to BlueZ D-Bus signals instead of polling.
if command -v dbus-monitor > /dev/null 2>&1; then
dbus-monitor --system "type='signal',sender='org.bluez'" 2> /dev/null |
while read -r line; do
[[ $line == *"PropertiesChanged"* ]] || continue
emit_throttled
done
fi
fi

View File

@ -1,6 +1,6 @@
[cpu_monitor]
command=~/.config/i3blocks/cpu_monitor.sh
interval=5
interval=10
markup=pango
@ -12,13 +12,13 @@ markup=pango
[motherboard_temperature]
command=~/.config/i3blocks/motherboard_temp.sh
interval=5
interval=30
markup=pango
[memory]
command=~/.config/i3blocks/memory.sh
interval=5
interval=30
color=#50FA7B
@ -39,25 +39,25 @@ markup=pango
[bluetooth]
command=~/.config/i3blocks/bluetooth.sh
interval=5
interval=persist
color=#FFFFFF
[battery]
command=~/.config/i3blocks/battery_status.sh
interval=5
interval=60
markup=pango
[ethernet]
command=~/.config/i3blocks/ethernet.sh
interval=10
interval=persist
color=#FFFFFF
[wifi]
command=~/.config/i3blocks/wifi_monitor.sh
interval=10
interval=persist
color=#FFFFFF
@ -69,12 +69,12 @@ color=#FFFFFF
[warp]
command=~/.config/i3blocks/warp_status.sh
interval=60
interval=persist
[activitywatch]
command=~/.config/i3blocks/activitywatch_status.sh
interval=10
interval=persist
color=#FFFFFF

View File

@ -3,6 +3,11 @@
set -euo pipefail
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
find_ethernet_interface() {
local iface_path iface
for iface_path in /sys/class/net/*; do
@ -16,20 +21,56 @@ find_ethernet_interface() {
return 1
}
iface=$(find_ethernet_interface) || {
printf ' down\n'
exit 0
emit() {
local iface state addr_output output_line
iface=$(find_ethernet_interface) || {
output_line=' down'
if i3blocks_update_if_changed_key "ethernet_output" "$output_line"; then
printf '%s\n' "$output_line"
fi
return 0
}
read -r state < "/sys/class/net/${iface}/operstate"
if [[ $state != up ]]; then
output_line=' down'
if i3blocks_update_if_changed_key "ethernet_output" "$output_line"; then
printf '%s\n' "$output_line"
fi
return 0
fi
addr_output=$(ip -o -4 addr show dev "$iface" scope global 2> /dev/null) || addr_output=''
if [[ $addr_output =~ ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+) ]]; then
output_line="${BASH_REMATCH[1]}"
else
output_line=' down'
fi
if i3blocks_update_if_changed_key "ethernet_output" "$output_line"; then
printf '%s\n' "$output_line"
fi
}
read -r state < "/sys/class/net/${iface}/operstate"
if [[ $state != up ]]; then
printf ' down\n'
exit 0
fi
is_persist_mode() {
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
}
addr_output=$(ip -o -4 addr show dev "$iface" scope global 2> /dev/null) || addr_output=''
if [[ $addr_output =~ ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+) ]]; then
printf ' %s\n' "${BASH_REMATCH[1]}"
else
printf ' down\n'
emit_throttled() {
if ! i3blocks_should_emit_by_interval_key "ethernet_emit" "$EMIT_MIN_INTERVAL_S"; then
return 0
fi
emit
}
EMIT_MIN_INTERVAL_S=2
emit
if is_persist_mode; then
ip monitor link address route 2> /dev/null |
while read -r line; do
[[ $line == *"eth"* || $line == *"en"* || $line == *"inet "* ]] || continue
emit_throttled
done
fi

View File

@ -12,6 +12,11 @@
set -u
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
# Nerd Font glyph: display / desktop icon (U+F108).
ICON=$'\uf108'
@ -30,6 +35,15 @@ emit() {
"$color" "$ICON" "$temp" "$load"
}
emit_if_changed() {
local temp=$1 load=$2
if ! i3blocks_update_if_changed_key "gpu_metric" "$temp|$load"; then
return 0
fi
emit "$temp" "$load"
}
# Prefer NVIDIA if present (persist via --loop).
if command -v nvidia-smi > /dev/null 2>&1; then
# One child process for the lifetime of i3blocks; emits CSV every 5s.
@ -44,7 +58,7 @@ if command -v nvidia-smi > /dev/null 2>&1; then
load=${load## }
load=${load%% }
[[ -z $temp || -z $load ]] && continue
emit "$temp" "$load"
emit_if_changed "$temp" "$load"
done
exit 0
fi
@ -73,7 +87,7 @@ if [[ -n $amdgpu ]]; then
break
}
done
emit "$temp" "$load"
emit_if_changed "$temp" "$load"
exit 0
fi

View File

@ -0,0 +1,55 @@
#!/bin/bash
# Shared helpers for persist-mode i3blocks scripts.
set -u
declare -gA I3BLOCKS_LAST_TS=()
declare -gA I3BLOCKS_LAST_STATE=()
# Return current epoch seconds using bash builtin time formatting.
i3blocks_now_ts() {
if [[ -n ${I3BLOCKS_TEST_NOW_TS:-} ]]; then
printf '%s\n' "$I3BLOCKS_TEST_NOW_TS"
return 0
fi
printf '%(%s)T' -1
}
# Return 0 when enough time elapsed since last emit timestamp for key.
# Usage: if i3blocks_should_emit_by_interval_key "wifi" 2; then ...
i3blocks_should_emit_by_interval_key() {
local key=$1
local min_interval_s=$2
local now last
now=$(i3blocks_now_ts)
last=${I3BLOCKS_LAST_TS[$key]:-0}
if (( now - last < min_interval_s )); then
return 1
fi
I3BLOCKS_LAST_TS[$key]=$now
return 0
}
# Return 0 when new state differs from current value for key, then update.
# Usage: if i3blocks_update_if_changed_key "wifi_output" "$line"; then emit; fi
i3blocks_update_if_changed_key() {
local key=$1
local new_state=$2
local current_state
if [[ -v I3BLOCKS_LAST_STATE[$key] ]]; then
current_state=${I3BLOCKS_LAST_STATE[$key]}
else
current_state=''
fi
if [[ -v I3BLOCKS_LAST_STATE[$key] && $current_state == "$new_state" ]]; then
return 1
fi
I3BLOCKS_LAST_STATE[$key]=$new_state
return 0
}

View File

@ -3,30 +3,67 @@
set -euo pipefail
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
if ! command -v warp-cli > /dev/null 2>&1; then
echo " N/A"
exit 0
fi
status=''
while IFS= read -r line; do
is_persist_mode() {
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
}
read_status() {
local status line
status=''
while IFS= read -r line; do
case $line in
'Status update: '*)
status=${line#Status update: }
;;
esac
done < <(warp-cli status 2> /dev/null)
done < <(warp-cli status 2> /dev/null)
printf '%s\n' "$status"
}
if [[ $status == Connected ]]; then
emit_status() {
local status=$1
if [[ $status == Connected ]]; then
echo "🔒 !!! WARP CONNECTED !!!"
echo
echo "#FFFF00"
elif [[ $status == Disconnected ]]; then
elif [[ $status == Disconnected ]]; then
echo "WARP disconnected"
echo
echo "#00FF00"
else
else
echo "⚠️ ! WARP unknown !"
echo
echo "#FF0000"
fi
}
emit_if_changed() {
local status=$1
if ! i3blocks_update_if_changed_key "warp_status" "$status"; then
return 0
fi
emit_status "$status"
}
current_status=$(read_status)
emit_status "$current_status"
if is_persist_mode; then
i3blocks_update_if_changed_key "warp_status" "$current_status" >/dev/null || true
fi
if is_persist_mode; then
while true; do
sleep 60
current_status=$(read_status)
emit_if_changed "$current_status"
done
fi

View File

@ -3,6 +3,11 @@
set -euo pipefail
SCRIPT_DIR=${BASH_SOURCE[0]%/*}
[[ $SCRIPT_DIR == "${BASH_SOURCE[0]}" ]] && SCRIPT_DIR='.'
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$SCRIPT_DIR/persist_common.sh"
find_wifi_interface() {
local line
while IFS= read -r line; do
@ -16,14 +21,20 @@ find_wifi_interface() {
return 1
}
wifi_interface=$(find_wifi_interface) || {
echo " down"
exit 0
}
emit() {
local wifi_interface ssid signal ip_address line fields i output_line
ssid=''
signal=''
while IFS= read -r line; do
wifi_interface=$(find_wifi_interface) || {
output_line=' down'
if i3blocks_update_if_changed_key "wifi_output" "$output_line"; then
echo "$output_line"
fi
return 0
}
ssid=''
signal=''
while IFS= read -r line; do
case $line in
'SSID: '*)
ssid=${line#SSID: }
@ -36,15 +47,18 @@ while IFS= read -r line; do
ssid=''
;;
esac
done < <(iw dev "$wifi_interface" link 2> /dev/null)
done < <(iw dev "$wifi_interface" link 2> /dev/null)
if [[ -z $ssid ]]; then
echo " down"
exit 0
fi
if [[ -z $ssid ]]; then
output_line=' down'
if i3blocks_update_if_changed_key "wifi_output" "$output_line"; then
echo "$output_line"
fi
return 0
fi
ip_address=''
while IFS= read -r line; do
ip_address=''
while IFS= read -r line; do
[[ $line == *' inet '* ]] || continue
read -r -a fields <<< "$line"
for ((i = 0; i < ${#fields[@]}; i++)); do
@ -53,10 +67,38 @@ while IFS= read -r line; do
break 2
fi
done
done < <(ip -o -4 addr show dev "$wifi_interface" scope global 2> /dev/null)
done < <(ip -o -4 addr show dev "$wifi_interface" scope global 2> /dev/null)
if [[ -n $ip_address ]]; then
echo "$ssid ($signal dBm) $ip_address"
else
echo "$ssid ($signal dBm)"
if [[ -n $ip_address ]]; then
output_line="$ssid ($signal dBm) $ip_address"
else
output_line="$ssid ($signal dBm)"
fi
if i3blocks_update_if_changed_key "wifi_output" "$output_line"; then
echo "$output_line"
fi
}
is_persist_mode() {
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
}
emit_throttled() {
if ! i3blocks_should_emit_by_interval_key "wifi_emit" "$EMIT_MIN_INTERVAL_S"; then
return 0
fi
emit
}
EMIT_MIN_INTERVAL_S=2
emit
if is_persist_mode; then
ip monitor link address route 2> /dev/null |
while read -r line; do
[[ $line == *"wlan"* || $line == *"wl"* || $line == *"inet "* ]] || continue
emit_throttled
done
fi

View File

@ -26,9 +26,10 @@ fi
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism"
mkdir -p "$LOG_DIR" 2> /dev/null || true
export LOG_FILE="$LOG_DIR/music-parallelism.log"
CHECK_INTERVAL=3
FAST_CHECK_INTERVAL=2
IDLE_CHECK_INTERVAL=10
CHECK_INTERVAL=15
FAST_CHECK_INTERVAL=5
IDLE_CHECK_INTERVAL=30
ENFORCEMENT_COOLDOWN=20
# Override focus apps with extended list for this script
FOCUS_APPS_WINDOWS=(
@ -88,14 +89,9 @@ find_music_services() {
printf -v music_pattern '%s|' "${MUSIC_SERVICES[@]}"
music_pattern="${music_pattern%|}" # strip trailing |
# 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"
# Check processes (single fork, no per-PID helpers)
if pgrep -i -f "$music_pattern" &> /dev/null; then
found_services+=("music process")
fi
# Check windows (use optimized is_focus_app_running logic: single xdotool regex call)
@ -118,69 +114,24 @@ find_music_services() {
# Kill music services
kill_music_services() {
local killed=false
local process_pattern='youtube-music|spotify|tidal|deezer|Amazon Music|amazon music'
local window_pattern='YouTube Music|music\.youtube\.com|music\.apple\.com|soundcloud\.com|pandora\.com|deezer\.com|tidal\.com'
# Kill YouTube Music browser tabs
# YouTube Music runs in browser, so we need to close specific tabs
# We use xdotool to find and close windows with "YouTube Music" or "music.youtube.com"
# Close browser tabs for web-based music services via one xdotool search
if command -v xdotool &> /dev/null; then
# Find windows with YouTube Music in title
local yt_music_windows
yt_music_windows=$(xdotool search --name "YouTube Music" 2> /dev/null || true)
for wid in $yt_music_windows; do
if [[ -n $wid ]]; then
# Get window name for logging
local wname
wname=$(xdotool getwindowname "$wid" 2> /dev/null || echo "unknown")
# Only close if it's YouTube Music, not regular YouTube
if [[ $wname == *"YouTube Music"* ]] || [[ $wname == *"music.youtube.com"* ]]; then
log_message "Closing YouTube Music window: $wname (ID: $wid)"
xdotool windowclose "$wid" 2> /dev/null || true
killed=true
fi
fi
done
fi
# Kill YouTube Music Electron app
if pgrep -f "youtube-music" &> /dev/null; then
log_message "Killing YouTube Music app"
pkill -9 -f "youtube-music" 2> /dev/null || true
killed=true
fi
# Kill Spotify
if pgrep -x "spotify" &> /dev/null; then
log_message "Killing Spotify"
pkill -9 -x "spotify" 2> /dev/null || true
killed=true
fi
# Kill other music streaming app processes
local music_processes=("tidal" "deezer" "Amazon Music")
for proc in "${music_processes[@]}"; do
if pgrep -i -f "$proc" &> /dev/null; then
log_message "Killing $proc"
pkill -9 -i -f "$proc" 2> /dev/null || true
killed=true
fi
done
# Close browser tabs for web-based music services
if command -v xdotool &> /dev/null; then
local web_music_patterns=("music.apple.com" "soundcloud.com" "pandora.com" "deezer.com" "tidal.com")
for pattern in "${web_music_patterns[@]}"; do
local windows
windows=$(xdotool search --name "$pattern" 2> /dev/null || true)
local windows wid
windows=$(xdotool search --name "$window_pattern" 2> /dev/null || true)
for wid in $windows; do
if [[ -n $wid ]]; then
local wname
wname=$(xdotool getwindowname "$wid" 2> /dev/null || echo "unknown")
log_message "Closing music service window: $wname (ID: $wid)"
[[ -n $wid ]] || continue
xdotool windowclose "$wid" 2> /dev/null || true
killed=true
done
fi
done
done
# Kill app processes with one regex-based pkill
if pgrep -i -f "$process_pattern" &> /dev/null; then
pkill -9 -i -f "$process_pattern" 2> /dev/null || true
killed=true
fi
if $killed; then
@ -206,24 +157,30 @@ notify_user() {
# 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() {
local next_enforcement_ts=0
local current_ts=0
local focus_app=""
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: 0.5s when focus app active, 3s when idle (optimized for lower fork overhead)"
log_message "Polling: ${FAST_CHECK_INTERVAL}s active, ${IDLE_CHECK_INTERVAL}s idle, ${ENFORCEMENT_COOLDOWN}s enforcement cooldown"
while true; do
# Only check if focus app is running (uses optimized is_focus_app_running from common.sh)
if is_focus_app_running &> /dev/null; then
# 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
if focus_app=$(is_focus_app_running 2> /dev/null); then
current_ts=$(get_timestamp)
if (( current_ts >= next_enforcement_ts )); then
if find_music_services > /dev/null 2>&1; then
if kill_music_services; then
notify_user "$focus_app"
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
fi
next_enforcement_ts=$((current_ts + ENFORCEMENT_COOLDOWN))
fi
sleep "$FAST_CHECK_INTERVAL"
else
# No focus app detected: use longer sleep to reduce fork overhead significantly
next_enforcement_ts=0
sleep "$IDLE_CHECK_INTERVAL"
fi
done
@ -328,7 +285,7 @@ show_usage() {
echo ""
echo "Commands:"
echo " monitor - Start monitoring (default, checks every ${CHECK_INTERVAL}s)"
echo " instant - Instant monitoring (checks every 0.5s for immediate kill)"
echo " instant - Instant monitoring (${FAST_CHECK_INTERVAL}s active / ${IDLE_CHECK_INTERVAL}s idle)"
echo " status - Show current status of focus apps and music services"
echo " kill - Immediately kill all music services"
echo " help - Show this help message"

View File

@ -1,6 +1,8 @@
#!/bin/bash
# filepath: /home/kuhy/linux-configuration/scripts/install_pacman_wrapper.sh
set -euo pipefail
# Auto-sudo functionality
if [ "$EUID" -ne 0 ]; then
echo "Executing with sudo..."
@ -22,12 +24,16 @@ WORDS_SOURCE="$(dirname "$0")/words.txt"
BLOCKED_SOURCE="$(dirname "$0")/pacman_blocked_keywords.txt"
WHITELIST_SOURCE="$(dirname "$0")/pacman_whitelist.txt"
GREYLIST_SOURCE="$(dirname "$0")/pacman_greylist.txt"
MAKEPKG_CAPPED_SOURCE="$(dirname "$0")/makepkg_capped.sh"
MKPKG_SOURCE="$(dirname "$0")/mkpkg.sh"
INSTALL_DIR="/usr/local/bin"
WRAPPER_DEST="${INSTALL_DIR}/pacman_wrapper"
WORDS_DEST="${INSTALL_DIR}/words.txt"
BLOCKED_DEST="${INSTALL_DIR}/pacman_blocked_keywords.txt"
WHITELIST_DEST="${INSTALL_DIR}/pacman_whitelist.txt"
GREYLIST_DEST="${INSTALL_DIR}/pacman_greylist.txt"
MAKEPKG_CAPPED_DEST="${INSTALL_DIR}/makepkg_capped"
MKPKG_DEST="${INSTALL_DIR}/mkpkg"
INTEGRITY_DIR="/var/lib/pacman-wrapper"
INTEGRITY_FILE="${INTEGRITY_DIR}/policy.sha256"
LEECHBLOCK_INSTALLER_SOURCE="$(dirname "$0")/../install_leechblock.sh"
@ -41,6 +47,57 @@ LEECHBLOCK_SEEDER_DEST="${LEECHBLOCK_INSTALL_DIR}/seed_leechblock_storage.js"
VBOX_ENFORCE_SOURCE="$(dirname "$0")/../virtualbox/enforce_vbox_hosts.sh"
VBOX_INSTALL_DIR="/usr/local/share/digital_wellbeing/virtualbox"
VBOX_ENFORCE_DEST="${VBOX_INSTALL_DIR}/enforce_vbox_hosts.sh"
declare -a RELock_FILES=()
is_immutable_file() {
local file_path="$1"
[[ -e "$file_path" ]] || return 1
[[ $(lsattr -d "$file_path" 2>/dev/null | awk '{print $1}') == *i* ]]
}
unlock_immutable_file_if_needed() {
local file_path="$1"
if ! command -v chattr >/dev/null 2>&1; then
return 0
fi
if is_immutable_file "$file_path"; then
chattr -i "$file_path"
RELock_FILES+=("$file_path")
fi
}
relock_files_on_exit() {
if ! command -v chattr >/dev/null 2>&1; then
return
fi
for file_path in "${RELock_FILES[@]}"; do
[[ -e "$file_path" ]] || continue
chattr +i "$file_path" 2>/dev/null || true
done
}
copy_managed_file() {
local source_file="$1"
local dest_file="$2"
local required="$3"
local label="$4"
if [[ ! -f "$source_file" ]]; then
if [[ "$required" == "required" ]]; then
echo -e "${RED}Error:${NC} Missing required ${label} at ${source_file}" >&2
exit 1
fi
echo -e "${YELLOW}Warning:${NC} Missing ${label} at ${source_file}" >&2
return
fi
unlock_immutable_file_if_needed "$dest_file"
cp "$source_file" "$dest_file"
}
trap relock_files_on_exit EXIT
# Check if script is run as root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Please run as root${NC}"
@ -57,26 +114,16 @@ echo -e "${CYAN}Installing pacman wrapper...${NC}"
# Install the wrapper script
echo -e "${BLUE}Copying wrapper script to ${WRAPPER_DEST}...${NC}"
cp "$WRAPPER_SOURCE" "$WRAPPER_DEST"
cp "$WORDS_SOURCE" "$WORDS_DEST"
if [ -f "$BLOCKED_SOURCE" ]; then
cp "$BLOCKED_SOURCE" "$BLOCKED_DEST"
else
echo -e "${YELLOW}Warning:${NC} Missing blocked keywords source at ${BLOCKED_SOURCE}${NC}"
fi
if [ -f "$WHITELIST_SOURCE" ]; then
cp "$WHITELIST_SOURCE" "$WHITELIST_DEST"
else
echo -e "${YELLOW}Warning:${NC} Missing whitelist source at ${WHITELIST_SOURCE}${NC}"
fi
if [ -f "$GREYLIST_SOURCE" ]; then
cp "$GREYLIST_SOURCE" "$GREYLIST_DEST"
else
echo -e "${YELLOW}Warning:${NC} Missing greylist source at ${GREYLIST_SOURCE}${NC}"
fi
copy_managed_file "$WRAPPER_SOURCE" "$WRAPPER_DEST" required "wrapper script"
copy_managed_file "$WORDS_SOURCE" "$WORDS_DEST" required "words list"
copy_managed_file "$BLOCKED_SOURCE" "$BLOCKED_DEST" required "blocked keywords list"
copy_managed_file "$WHITELIST_SOURCE" "$WHITELIST_DEST" optional "whitelist"
copy_managed_file "$GREYLIST_SOURCE" "$GREYLIST_DEST" required "greylist"
chmod +x "$WRAPPER_DEST"
copy_managed_file "$MAKEPKG_CAPPED_SOURCE" "$MAKEPKG_CAPPED_DEST" required "makepkg capped wrapper"
chmod +x "$MAKEPKG_CAPPED_DEST"
copy_managed_file "$MKPKG_SOURCE" "$MKPKG_DEST" required "mkpkg helper"
chmod +x "$MKPKG_DEST"
chmod 644 "$WORDS_DEST" "$BLOCKED_DEST" "$WHITELIST_DEST" "$GREYLIST_DEST" 2> /dev/null || true
# Automatically use symbolic link installation method
@ -97,6 +144,7 @@ chmod 755 "$INTEGRITY_DIR"
# Generate checksums of policy files for integrity verification
echo -e "${BLUE}Generating integrity checksums for policy files...${NC}"
unlock_immutable_file_if_needed "$INTEGRITY_FILE"
# Ensure all critical policy files exist before checksumming
missing_files=()
@ -181,6 +229,12 @@ echo -e "${BLUE}Creating symbolic link...${NC}"
ln -sf "$WRAPPER_DEST" /usr/bin/pacman
echo -e "${GREEN}Installation complete!${NC}"
echo -e "Pacman is now wrapped. The original pacman is available at ${CYAN}/usr/bin/pacman.orig${NC}"
if [ -f "$MAKEPKG_CAPPED_DEST" ]; then
echo -e "Run constrained package builds with: ${CYAN}pacman --makepkg-capped <args>${NC}"
fi
if [ -f "$MKPKG_DEST" ]; then
echo -e "Shortcut available: ${CYAN}mkpkg <args>${NC}"
fi
echo -e "${CYAN}Policy files are now protected with immutable attributes.${NC}"
if [ -f "$VBOX_ENFORCE_DEST" ]; then
echo -e "${CYAN}VirtualBox VMs will automatically be configured to use host's /etc/hosts.${NC}"

View File

@ -0,0 +1,43 @@
#!/bin/bash
# Run makepkg inside a constrained user scope to protect desktop responsiveness.
set -euo pipefail
CPU_QUOTA="${MAKEPKG_CPU_QUOTA:-70%}"
MEMORY_MAX="${MAKEPKG_MEMORY_MAX:-12G}"
TASKS_MAX="${MAKEPKG_TASKS_MAX:-2048}"
NICE_LEVEL="${MAKEPKG_NICE_LEVEL:-10}"
IONICE_CLASS="${MAKEPKG_IONICE_CLASS:-2}"
IONICE_LEVEL="${MAKEPKG_IONICE_LEVEL:-7}"
if ! command -v makepkg >/dev/null 2>&1; then
echo "makepkg_capped: makepkg not found in PATH" >&2
exit 127
fi
run_makepkg_fallback() {
exec ionice -c "$IONICE_CLASS" -n "$IONICE_LEVEL" \
nice -n "$NICE_LEVEL" \
makepkg "$@"
}
if ! command -v systemd-run >/dev/null 2>&1; then
echo "makepkg_capped: systemd-run unavailable, falling back to local limits" >&2
run_makepkg_fallback "$@"
fi
# Keep builds in the user manager scope. If this fails (no user manager),
# gracefully fall back to ionice+nice only.
if ! systemd-run --user --scope --quiet true 2>/dev/null; then
echo "makepkg_capped: user systemd scope unavailable, falling back" >&2
run_makepkg_fallback "$@"
fi
exec systemd-run --user --scope --quiet --same-dir --collect \
-p "CPUQuota=${CPU_QUOTA}" \
-p "MemoryMax=${MEMORY_MAX}" \
-p "MemorySwapMax=0" \
-p "TasksMax=${TASKS_MAX}" \
ionice -c "$IONICE_CLASS" -n "$IONICE_LEVEL" \
nice -n "$NICE_LEVEL" \
makepkg "$@"

View File

@ -0,0 +1,8 @@
#!/bin/bash
# Convenience wrapper for constrained Arch package builds.
set -euo pipefail
PACMAN_WRAPPER_BIN="/usr/bin/pacman"
exec "$PACMAN_WRAPPER_BIN" --makepkg-capped "$@"

View File

@ -12,6 +12,7 @@ BOLD='\033[1m'
NC='\033[0m' # No Color
PACMAN_BIN="/usr/bin/pacman"
MAKEPKG_CAPPED_BIN="/usr/local/bin/makepkg_capped"
declare -a BLOCKED_KEYWORDS_LIST=()
declare -a WHITELISTED_NAMES_LIST=()
@ -241,6 +242,22 @@ function show_help() {
echo ""
echo "Additional commands:"
echo " --help-wrapper Show this help message"
echo " --makepkg-capped Run makepkg in a constrained systemd user scope"
echo " (forward remaining args to makepkg)"
}
run_makepkg_capped() {
if [[ ! -x $MAKEPKG_CAPPED_BIN ]]; then
echo -e "${RED}makepkg capped wrapper not found at ${MAKEPKG_CAPPED_BIN}${NC}" >&2
echo -e "${YELLOW}Run install_pacman_wrapper.sh to install it.${NC}" >&2
return 1
fi
if [[ $EUID -eq 0 && -n ${SUDO_USER:-} ]]; then
exec sudo -u "$SUDO_USER" "$MAKEPKG_CAPPED_BIN" "$@"
fi
exec "$MAKEPKG_CAPPED_BIN" "$@"
}
# Function to display a message before executing
@ -707,6 +724,11 @@ if [[ $1 == "--help-wrapper" ]]; then
exit 0
fi
if [[ ${1:-} == "--makepkg-capped" ]]; then
shift
run_makepkg_capped "$@"
fi
# CRITICAL: Verify policy file integrity before any operations
if ! verify_policy_integrity; then
exit 1

View File

@ -187,11 +187,11 @@ is_focus_app_running() {
local regex wid
printf -v regex '%s|' "${FOCUS_APPS_WINDOWS[@]}"
regex="${regex%|}" # strip trailing |
wid=$(xdotool search --name "$regex" 2>/dev/null | head -1 || true)
if [[ -n $wid ]]; then
xdotool getwindowname "$wid" 2>/dev/null || echo "focus app"
while IFS= read -r wid; do
[[ -n $wid ]] || continue
echo "focus app"
return 0
fi
done < <(xdotool search --name "$regex" 2>/dev/null)
fi
# Check specific processes via /proc (no fork)

View File

@ -32,7 +32,11 @@ for id in ${ID:-} ${ID_LIKE:-}; do
FAMILY="arch"
break
;;
debian | ubuntu | linuxmint | pop | elementary)
debian | ubuntu | linuxmint | pop)
FAMILY="debian"
break
;;
elementary)
FAMILY="debian"
break
;;
@ -152,20 +156,24 @@ if ! command -v nvidia-smi >/dev/null 2>&1; then
exit 1
fi
current_day() {
printf '%(%Y%m%d)T' -1
}
while true; do
day="$(date +%Y%m%d)"
day="$(current_day)"
out_file="$LOG_DIR/pmon-${day}.log"
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 [[ "$(date +%Y%m%d)" != "$day" ]]; then
if [[ "$(current_day)" != "$day" ]]; then
kill "$pmon_pid" >/dev/null 2>&1 || true
wait "$pmon_pid" || true
break
fi
read -r -t 20 _ || true
sleep 60
done
done

View File

@ -528,6 +528,39 @@ def _pmon_fields(line: str) -> list[str] | None:
return s.split()
def _normalize_pmon_command(command_fields: list[str]) -> str:
"""Normalize pmon command fields into a stable process-ish name.
`nvidia-smi pmon -o DT` emits fixed numeric columns followed by a command
field that can include whitespace. We prefer the *first* non-option token
(usually executable) and normalize it to a basename.
"""
tokens = [token.strip().strip("\"'") for token in command_fields if token.strip()]
if not tokens:
return "unknown"
selected = tokens[0]
if selected.startswith("-"):
for candidate in tokens[1:]:
if not candidate.startswith("-"):
selected = candidate
break
name = Path(selected).name.strip(";,:")
if not name:
return "unknown"
return name
def _pid_comm_name(pid: int) -> str | None:
"""Return `/proc/<pid>/comm` basename when available."""
try:
comm = Path(f"/proc/{pid}/comm").read_text(encoding="utf-8").strip()
except OSError:
return None
return Path(comm).name if comm else None
def aggregate_pmon(
log: Path,
progress: _Progress,
@ -563,7 +596,10 @@ def _ingest_pmon_row(parts: list[str], agg: dict[str, GpuAgg]) -> int:
return 0
sm_raw = parts[5]
mem_raw = parts[6]
name = parts[-1]
command_fields = parts[11:]
name = _normalize_pmon_command(command_fields)
if name == "unknown":
name = _pid_comm_name(pid) or "unknown"
sm = float(sm_raw) if sm_raw != "-" else 0.0
mem = float(mem_raw) if mem_raw != "-" else 0.0
entry = agg.setdefault(name, GpuAgg(name=name))

View File

@ -0,0 +1 @@
"""Tests for linux_configuration scripts and tooling."""

View File

@ -8,6 +8,9 @@ REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
I3BLOCKS_DIR="$REPO_DIR/i3-configuration/i3blocks"
CONFIG_FILE="$I3BLOCKS_DIR/config"
printf 'Running persist_common helper regression checks...\n'
bash "$SCRIPT_DIR/test_i3blocks_persist_common.sh"
TMP_DIR=$(mktemp -d)
BIN_DIR="$TMP_DIR/bin"
mkdir -p "$BIN_DIR"
@ -164,6 +167,58 @@ grep -q '^command=~/.config/i3blocks/ethernet.sh$' "$CONFIG_FILE" \
|| fail 'ethernet block should call ethernet.sh'
grep -q '^command=~/.config/i3blocks/disk.sh$' "$CONFIG_FILE" \
|| fail 'disk block should call disk.sh'
grep -q '^interval=10$' "$CONFIG_FILE" \
|| fail 'cpu block should poll at 10s interval'
grep -A2 '^\[motherboard_temperature\]$' "$CONFIG_FILE" | grep -q '^interval=30$' \
|| fail 'motherboard block should poll at 30s interval'
grep -A2 '^\[memory\]$' "$CONFIG_FILE" | grep -q '^interval=30$' \
|| fail 'memory block should poll at 30s interval'
grep -A2 '^\[bluetooth\]$' "$CONFIG_FILE" | grep -q '^interval=persist$' \
|| fail 'bluetooth block should use persist mode'
grep -A2 '^\[battery\]$' "$CONFIG_FILE" | grep -q '^interval=60$' \
|| fail 'battery block should poll at 60s interval'
grep -A2 '^\[ethernet\]$' "$CONFIG_FILE" | grep -q '^interval=persist$' \
|| fail 'ethernet block should use persist mode'
grep -A2 '^\[wifi\]$' "$CONFIG_FILE" | grep -q '^interval=persist$' \
|| fail 'wifi block should use persist mode'
grep -A2 '^\[activitywatch\]$' "$CONFIG_FILE" | grep -q '^interval=persist$' \
|| fail 'activitywatch block should use persist mode'
grep -A2 '^\[warp\]$' "$CONFIG_FILE" | grep -q '^interval=persist$' \
|| fail 'warp block should use persist mode'
printf 'Checking focus detection path avoids extra xdotool lookups...\n'
! grep -Fq "xdotool getwindowname \"\$wid\"" "$REPO_DIR/scripts/lib/common.sh" \
|| fail 'focus detection should not call xdotool getwindowname in hot path'
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'
printf 'Checking GPU/WARP dedupe guards exist...\n'
grep -Fq 'emit_if_changed()' "$I3BLOCKS_DIR/gpu_monitor.sh" \
|| fail 'gpu monitor should dedupe repeated identical samples'
grep -Fq 'emit_if_changed()' "$I3BLOCKS_DIR/warp_status.sh" \
|| fail 'warp status should dedupe repeated identical states'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/bluetooth.sh" \
|| fail 'bluetooth script should use shared persist helper'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/wifi_monitor.sh" \
|| fail 'wifi script should use shared persist helper'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/ethernet.sh" \
|| fail 'ethernet script should use shared persist helper'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/activitywatch_status.sh" \
|| fail 'activitywatch script should use shared persist helper'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/gpu_monitor.sh" \
|| fail 'gpu script should use shared persist helper'
grep -Fq "source \"\$SCRIPT_DIR/persist_common.sh\"" "$I3BLOCKS_DIR/warp_status.sh" \
|| fail 'warp script should use shared persist helper'
grep -Fq 'i3blocks_update_if_changed_key "bluetooth_state"' "$I3BLOCKS_DIR/bluetooth.sh" \
|| fail 'bluetooth script should dedupe unchanged state'
grep -Fq 'i3blocks_update_if_changed_key "wifi_output"' "$I3BLOCKS_DIR/wifi_monitor.sh" \
|| fail 'wifi script should dedupe unchanged output'
grep -Fq 'i3blocks_update_if_changed_key "ethernet_output"' "$I3BLOCKS_DIR/ethernet.sh" \
|| 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'
printf 'Checking bluetooth block behavior and fork count...\n'
bluetooth_output=$(PATH="$BIN_DIR:$PATH" bash "$I3BLOCKS_DIR/bluetooth.sh")

View File

@ -0,0 +1,146 @@
#!/bin/bash
# Regression tests for i3blocks persist_common helper functions.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
HELPER="$REPO_DIR/i3-configuration/i3blocks/persist_common.sh"
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$HELPER"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_eq() {
local expected=$1
local actual=$2
local context=$3
if [[ "$expected" != "$actual" ]]; then
fail "$context (expected '$expected', actual '$actual')"
fi
}
count_execs() {
local script_path=$1
local log_file=$2
strace -f -o "$log_file" -e trace=execve bash "$script_path" >/dev/null 2>&1
grep -c 'execve(' "$log_file"
}
printf 'Checking interval gating allows first emit per key...\n'
I3BLOCKS_LAST_TS=()
I3BLOCKS_TEST_NOW_TS=100
i3blocks_should_emit_by_interval_key "wifi" 5 || fail 'first interval check should allow emit'
assert_eq '100' "${I3BLOCKS_LAST_TS[wifi]}" 'first emit should store current timestamp'
printf 'Checking interval gating blocks too-soon second emit...\n'
I3BLOCKS_TEST_NOW_TS=102
if i3blocks_should_emit_by_interval_key "wifi" 5; then
fail 'second interval check should block when interval has not elapsed'
fi
assert_eq '100' "${I3BLOCKS_LAST_TS[wifi]}" 'blocked emit must not overwrite timestamp'
printf 'Checking repeated blocked emits never mutate timestamp...\n'
for _ in {1..200}; do
if i3blocks_should_emit_by_interval_key "wifi" 5; then
fail 'repeated blocked interval checks must remain blocked'
fi
done
assert_eq '100' "${I3BLOCKS_LAST_TS[wifi]}" 'repeated blocked checks must preserve original timestamp'
printf 'Checking interval gating allows later emit...\n'
I3BLOCKS_TEST_NOW_TS=106
i3blocks_should_emit_by_interval_key "wifi" 5 || fail 'emit should pass after interval elapsed'
assert_eq '106' "${I3BLOCKS_LAST_TS[wifi]}" 'allowed emit should update timestamp'
printf 'Checking interval gating is key-isolated...\n'
I3BLOCKS_TEST_NOW_TS=103
i3blocks_should_emit_by_interval_key "ethernet" 5 || fail 'new key should allow first emit independently'
assert_eq '103' "${I3BLOCKS_LAST_TS[ethernet]}" 'independent key should store its own timestamp'
unset I3BLOCKS_TEST_NOW_TS
printf 'Checking changed-state helper allows first state...\n'
I3BLOCKS_LAST_STATE=()
i3blocks_update_if_changed_key "wifi" "connected" || fail 'first state should be treated as changed'
assert_eq 'connected' "${I3BLOCKS_LAST_STATE[wifi]}" 'first changed state should be stored'
printf 'Checking changed-state helper blocks identical state...\n'
if i3blocks_update_if_changed_key "wifi" "connected"; then
fail 'identical state should be treated as unchanged'
fi
printf 'Checking changed-state helper allows new state...\n'
i3blocks_update_if_changed_key "wifi" "disconnected" || fail 'different state should be treated as changed'
assert_eq 'disconnected' "${I3BLOCKS_LAST_STATE[wifi]}" 'new state should replace old state'
printf 'Checking empty string first state is treated as changed...\n'
I3BLOCKS_LAST_STATE=()
i3blocks_update_if_changed_key "warp" "" || fail 'first empty state should be treated as changed'
if i3blocks_update_if_changed_key "warp" ""; then
fail 'second empty state should be treated as unchanged'
fi
printf 'Checking changed-state helper is key-isolated...\n'
i3blocks_update_if_changed_key "bluetooth" "on" || fail 'different key should track independently'
assert_eq 'on' "${I3BLOCKS_LAST_STATE[bluetooth]}" 'second key should store independent state'
assert_eq '' "${I3BLOCKS_LAST_STATE[warp]}" 'first key state should remain unchanged'
printf 'Checking interleaved multi-key updates remain isolated...\n'
I3BLOCKS_LAST_TS=()
I3BLOCKS_LAST_STATE=()
export I3BLOCKS_TEST_NOW_TS
for i in {1..50}; do
I3BLOCKS_TEST_NOW_TS=$((1000 + i))
i3blocks_should_emit_by_interval_key "wifi" 0 || fail 'wifi interleaved emit should pass'
i3blocks_update_if_changed_key "wifi_state" "wifi-$((i % 3))" || true
I3BLOCKS_TEST_NOW_TS=$((2000 + i))
i3blocks_should_emit_by_interval_key "wifi_monitor" 0 || fail 'wifi_monitor interleaved emit should pass'
i3blocks_update_if_changed_key "wifi_monitor_state" "monitor-$((i % 4))" || true
I3BLOCKS_TEST_NOW_TS=$((3000 + i))
i3blocks_should_emit_by_interval_key "ethernet" 0 || fail 'ethernet interleaved emit should pass'
i3blocks_update_if_changed_key "ethernet_state" "eth-$((i % 2))" || true
done
assert_eq '1050' "${I3BLOCKS_LAST_TS[wifi]}" 'wifi key should retain its own timestamp series'
assert_eq '2050' "${I3BLOCKS_LAST_TS[wifi_monitor]}" 'wifi_monitor key should retain its own timestamp series'
assert_eq '3050' "${I3BLOCKS_LAST_TS[ethernet]}" 'ethernet key should retain its own timestamp series'
assert_eq 'wifi-2' "${I3BLOCKS_LAST_STATE[wifi_state]}" 'wifi_state should keep independent final state'
assert_eq 'monitor-2' "${I3BLOCKS_LAST_STATE[wifi_monitor_state]}" 'wifi_monitor_state should keep independent final state'
assert_eq 'eth-0' "${I3BLOCKS_LAST_STATE[ethernet_state]}" 'ethernet_state should keep independent final state'
unset I3BLOCKS_TEST_NOW_TS
printf 'Checking helper hot path stays fork-free under load...\n'
fork_probe="$TMP_DIR/persist_common_fork_probe.sh"
cat >"$fork_probe" <<EOF
#!/bin/bash
set -euo pipefail
# shellcheck source=linux_configuration/i3-configuration/i3blocks/persist_common.sh
source "$HELPER"
I3BLOCKS_LAST_TS=()
I3BLOCKS_LAST_STATE=()
for i in {1..2000}; do
i3blocks_should_emit_by_interval_key "wifi" 0
i3blocks_update_if_changed_key "wifi_state" "state-\$((i % 3))"
done
EOF
chmod +x "$fork_probe"
exec_count=$(count_execs "$fork_probe" "$TMP_DIR/fork_probe.trace")
assert_eq '1' "$exec_count" 'persist helper hot path should not fork external commands'
printf 'persist_common helper regression tests passed.\n'

View File

@ -0,0 +1,31 @@
#!/bin/bash
# Regression checks for makepkg_capped wrapper.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
WRAPPER="$REPO_DIR/scripts/digital_wellbeing/pacman/makepkg_capped.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
printf 'Checking makepkg_capped exists...\n'
[[ -f "$WRAPPER" ]] || fail 'makepkg_capped.sh is missing'
printf 'Checking makepkg_capped syntax...\n'
bash -n "$WRAPPER"
printf 'Checking systemd scope limits are present...\n'
grep -Fq 'CPUQuota=' "$WRAPPER" || fail 'CPUQuota setting missing'
grep -Fq 'MemoryMax=' "$WRAPPER" || fail 'MemoryMax setting missing'
grep -Fq 'MemorySwapMax=0' "$WRAPPER" || fail 'MemorySwapMax=0 setting missing'
grep -Fq 'TasksMax=' "$WRAPPER" || fail 'TasksMax setting missing'
printf 'Checking graceful fallback path exists...\n'
grep -Fq 'run_makepkg_fallback' "$WRAPPER" || fail 'fallback function missing'
grep -Fq 'systemd-run --user --scope --quiet true' "$WRAPPER" || fail 'user-scope probe missing'
printf 'makepkg_capped regression checks passed.\n'

View File

@ -46,21 +46,21 @@ else
exit 1
fi
# Test 5: Verify hardcoded VirtualBox check exists
echo "[TEST 5] Verifying hardcoded VirtualBox check exists..."
if grep -q "is_virtualbox_package()" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ Hardcoded VirtualBox check function found"
# Test 5: Verify hardcoded VirtualBox cleanup function exists
echo "[TEST 5] Verifying hardcoded VirtualBox cleanup function exists..."
if grep -q "auto_remove_virtualbox_vms()" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ Hardcoded VirtualBox cleanup function found"
else
echo "✗ Hardcoded VirtualBox check function not found"
echo "✗ Hardcoded VirtualBox cleanup function not found"
exit 1
fi
# Test 6: Verify VirtualBox challenge function exists
echo "[TEST 6] Verifying VirtualBox challenge function exists..."
if grep -q "prompt_for_virtualbox_challenge()" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ VirtualBox challenge function found"
# Test 6: Verify VirtualBox cleanup uses VBoxManage directly
echo "[TEST 6] Verifying VirtualBox cleanup uses VBoxManage directly..."
if grep -q "VBoxManage" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ VirtualBox cleanup logic found"
else
echo "✗ VirtualBox challenge function not found"
echo "✗ VirtualBox cleanup logic not found"
exit 1
fi
@ -91,12 +91,12 @@ else
exit 1
fi
# Test 10: Verify VirtualBox enforcement is integrated
echo "[TEST 10] Verifying VirtualBox enforcement is integrated into wrapper..."
if grep -q "enforce_vbox_hosts_if_needed" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ VirtualBox enforcement integration found"
# Test 10: Verify VirtualBox cleanup enforcement is integrated
echo "[TEST 10] Verifying VirtualBox cleanup is integrated into wrapper..."
if grep -q "auto_remove_virtualbox_vms" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ VirtualBox cleanup integration found"
else
echo "✗ VirtualBox enforcement integration not found"
echo "✗ VirtualBox cleanup integration not found"
exit 1
fi
@ -119,6 +119,69 @@ else
exit 1
fi
# Test 13: Verify makepkg capped wrapper script syntax
echo "[TEST 13] Checking makepkg capped wrapper syntax..."
if bash -n "$WRAPPER_DIR/makepkg_capped.sh"; then
echo "✓ makepkg capped wrapper syntax is valid"
else
echo "✗ makepkg capped wrapper has syntax errors"
exit 1
fi
# Test 14: Verify pacman wrapper exposes makepkg capped command
echo "[TEST 14] Verifying pacman wrapper supports --makepkg-capped..."
if grep -q -- "--makepkg-capped" "$WRAPPER_DIR/pacman_wrapper.sh"; then
echo "✓ pacman wrapper makepkg capped command found"
else
echo "✗ pacman wrapper makepkg capped command missing"
exit 1
fi
# Test 15: Verify installer deploys makepkg capped wrapper
echo "[TEST 15] Verifying installer deploys makepkg capped wrapper..."
if grep -q "MAKEPKG_CAPPED" "$WRAPPER_DIR/install_pacman_wrapper.sh"; then
echo "✓ Installer includes makepkg capped deployment"
else
echo "✗ Installer does not include makepkg capped deployment"
exit 1
fi
# Test 16: Verify mkpkg helper script syntax
echo "[TEST 16] Checking mkpkg helper script syntax..."
if bash -n "$WRAPPER_DIR/mkpkg.sh"; then
echo "✓ mkpkg helper script syntax is valid"
else
echo "✗ mkpkg helper script has syntax errors"
exit 1
fi
# Test 17: Verify installer deploys mkpkg helper
echo "[TEST 17] Verifying installer deploys mkpkg helper..."
if grep -q "MKPKG" "$WRAPPER_DIR/install_pacman_wrapper.sh"; then
echo "✓ Installer includes mkpkg helper deployment"
else
echo "✗ Installer does not include mkpkg helper deployment"
exit 1
fi
# Test 18: Verify installer runs in strict mode
echo "[TEST 18] Verifying installer uses strict shell mode..."
if grep -q "set -euo pipefail" "$WRAPPER_DIR/install_pacman_wrapper.sh"; then
echo "✓ Installer strict mode enabled"
else
echo "✗ Installer strict mode not enabled"
exit 1
fi
# Test 19: Verify installer handles immutable files during updates
echo "[TEST 19] Verifying installer unlocks immutable files before copy/write..."
if grep -q "unlock_immutable_file_if_needed" "$WRAPPER_DIR/install_pacman_wrapper.sh"; then
echo "✓ Installer immutable-file handling found"
else
echo "✗ Installer immutable-file handling missing"
exit 1
fi
echo ""
echo "=== All Tests Passed! ==="
echo ""
@ -129,3 +192,6 @@ echo " ✓ Policy files are made immutable with chattr +i"
echo " ✓ VirtualBox has hardcoded restrictions (cannot bypass via file editing)"
echo " ✓ VirtualBox VMs are automatically configured to use host's /etc/hosts"
echo " ✓ Difficult word challenge for VirtualBox installation (7-letter words, 150 words, 120s)"
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"

View File

@ -0,0 +1,41 @@
#!/bin/bash
# Regression tests for nvidia-pmon logger installer template efficiency.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
INSTALLER="$REPO_DIR/scripts/system-maintenance/bin/install_usage_monitoring.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
logger_template=$(
awk '
/cat > "\$HOME\/.local\/bin\/nvidia-pmon-logger\.sh" << '\''SCRIPT'\''/ {capture=1; next}
capture && /^SCRIPT$/ {capture=0; exit}
capture {print}
' "$INSTALLER"
)
[[ -n $logger_template ]] || fail 'could not extract nvidia-pmon-logger template from installer'
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 fork-free date builtin...\n'
grep -q "printf '%(%Y%m%d)T' -1" <<< "$logger_template" \
|| fail 'logger template must use bash printf time builtin for current day'
printf 'Checking pmon logger template avoids external date command...\n'
! grep -q 'date +%Y%m%d' <<< "$logger_template" \
|| fail 'logger template must not call external date command in hot path'
printf 'Usage monitoring installer efficiency tests passed.\n'

View File

@ -0,0 +1,92 @@
"""Regression tests for pmon process-name normalization in usage_report."""
from __future__ import annotations
import importlib.util
from pathlib import Path
import sys
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import pytest
MODULE_PATH = (
Path(__file__).resolve().parents[1]
/ "scripts"
/ "system-maintenance"
/ "bin"
/ "usage_report.py"
)
SPEC = importlib.util.spec_from_file_location("usage_report", MODULE_PATH)
if SPEC is None or SPEC.loader is None:
msg = "could not load usage_report module"
raise RuntimeError(msg)
usage_report = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = usage_report
SPEC.loader.exec_module(usage_report)
def test_normalize_pmon_command_prefers_first_executable_token() -> None:
"""The parser should keep executable-like token, not trailing args."""
tokens = ["code-insiders", "--type=", "gpu-process", "Not"]
assert usage_report._normalize_pmon_command(tokens) == "code-insiders"
def test_normalize_pmon_command_skips_leading_option_tokens() -> None:
"""If the first token is an option, use the next non-option token."""
tokens = ["--type=", "code-insiders", "--flag"]
assert usage_report._normalize_pmon_command(tokens) == "code-insiders"
def test_ingest_pmon_row_uses_command_field_start_not_last_token() -> None:
"""Rows with command args should aggregate under process name, not args."""
row = [
"20260507",
"12:00:00",
"0",
"123",
"C",
"10",
"5",
"0",
"0",
"0",
"0",
"code-insiders",
"--type=",
"gpu-process",
]
agg: dict[str, object] = {}
consumed = usage_report._ingest_pmon_row(row, agg)
assert consumed == 1
assert "code-insiders" in agg
def test_ingest_pmon_row_falls_back_to_proc_comm_on_unknown(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""When command field is empty, parser can recover with /proc/<pid>/comm."""
row = [
"20260507",
"12:00:00",
"0",
"999",
"C",
"30",
"10",
"0",
"0",
"0",
"0",
]
agg: dict[str, object] = {}
monkeypatch.setattr(usage_report, "_pid_comm_name", lambda _pid: "python")
consumed = usage_report._ingest_pmon_row(row, agg)
assert consumed == 1
assert "python" in agg

View File

@ -9,6 +9,8 @@ import shutil
import signal
import subprocess
from python_pkg.steam_backlog_enforcer.game_install import PROTECTED_APP_IDS
logger = logging.getLogger(__name__)
@ -58,6 +60,8 @@ def enforce_allowed_game(
# Skip Steam client itself (app_id 0 or very low IDs).
if app_id == 0:
continue
if app_id in PROTECTED_APP_IDS:
continue
violations.append((pid, app_id))
if kill_unauthorized:

View File

@ -85,6 +85,7 @@ PROTECTED_APP_IDS = {
2252570,
220200,
3527290, # Peak
1331550,
}
STEAMAPPS_PATH = Path("~/.local/share/Steam/steamapps").expanduser()

View File

@ -10,7 +10,7 @@ ExecStart=/usr/bin/python3 -m python_pkg.steam_backlog_enforcer.main enforce
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONPATH=/home/kuhy/.local/lib/python3.14/site-packages
Environment=PYTHONPATH=/home/kuhy/testsAndMisc:/home/kuhy/.local/lib/python3.14/site-packages
Environment=HOME=/home/kuhy
# Hardening: enforcer must not be easily killed.
OOMScoreAdjust=-900

View File

@ -133,6 +133,25 @@ class TestEnforceAllowedGame:
result = enforce_allowed_game(None, kill_unauthorized=True)
assert result == []
def test_skips_protected_app_id(self) -> None:
"""Protected IDs must never be killed even if not the assigned game."""
with (
patch(
"python_pkg.steam_backlog_enforcer.enforcer.get_running_steam_game_pids",
return_value={100: 1331550, 200: 440},
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.PROTECTED_APP_IDS",
{1331550},
),
patch(
"python_pkg.steam_backlog_enforcer.enforcer.kill_process"
) as mock_kill,
):
result = enforce_allowed_game(440, kill_unauthorized=True)
assert result == []
mock_kill.assert_not_called()
class TestKillProcess:
"""Tests for kill_process."""