mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 12:43:12 +02:00
Harden runtime script deployment and enforce installer safety
This commit is contained in:
parent
1c90577b40
commit
1ebb667265
@ -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"
|
||||
}
|
||||
@ -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."
|
||||
]
|
||||
}
|
||||
@ -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
|
||||
echo "AW uninstalled"
|
||||
echo
|
||||
echo "#FF0000"
|
||||
elif check_running; then
|
||||
echo "AW on"
|
||||
echo
|
||||
echo "#00FF00"
|
||||
else
|
||||
echo "AW off"
|
||||
echo
|
||||
echo "#FF0000"
|
||||
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 [[ $state == 'on' ]]; then
|
||||
echo "AW on"
|
||||
echo
|
||||
echo "#00FF00"
|
||||
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
|
||||
|
||||
@ -3,25 +3,70 @@
|
||||
|
||||
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
|
||||
case $line in
|
||||
*'Connected: yes')
|
||||
connected='yes'
|
||||
;;
|
||||
*'Alias: '*)
|
||||
device=${line#*Alias: }
|
||||
;;
|
||||
esac
|
||||
done <<< "$bluetooth_info"
|
||||
get_bluetooth_info() {
|
||||
local info
|
||||
info=$(bluetoothctl info 2> /dev/null) || info=''
|
||||
printf '%s\n' "$info"
|
||||
}
|
||||
|
||||
if [[ $connected == yes && -n $device ]]; then
|
||||
echo " $device"
|
||||
echo
|
||||
echo "#50FA7B"
|
||||
else
|
||||
echo " Disconnected"
|
||||
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'
|
||||
;;
|
||||
*'Alias: '*)
|
||||
device=${line#*Alias: }
|
||||
;;
|
||||
esac
|
||||
done <<< "$bluetooth_info"
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
55
linux_configuration/i3-configuration/i3blocks/persist_common.sh
Executable file
55
linux_configuration/i3-configuration/i3blocks/persist_common.sh
Executable 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
|
||||
}
|
||||
@ -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
|
||||
case $line in
|
||||
'Status update: '*)
|
||||
status=${line#Status update: }
|
||||
;;
|
||||
esac
|
||||
done < <(warp-cli status 2> /dev/null)
|
||||
is_persist_mode() {
|
||||
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
|
||||
}
|
||||
|
||||
if [[ $status == Connected ]]; then
|
||||
echo "🔒 !!! WARP CONNECTED !!!"
|
||||
echo
|
||||
echo "#FFFF00"
|
||||
elif [[ $status == Disconnected ]]; then
|
||||
echo "WARP disconnected"
|
||||
echo
|
||||
echo "#00FF00"
|
||||
else
|
||||
echo "⚠️ ! WARP unknown !"
|
||||
echo
|
||||
echo "#FF0000"
|
||||
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)
|
||||
printf '%s\n' "$status"
|
||||
}
|
||||
|
||||
emit_status() {
|
||||
local status=$1
|
||||
if [[ $status == Connected ]]; then
|
||||
echo "🔒 !!! WARP CONNECTED !!!"
|
||||
echo
|
||||
echo "#FFFF00"
|
||||
elif [[ $status == Disconnected ]]; then
|
||||
echo "WARP disconnected"
|
||||
echo
|
||||
echo "#00FF00"
|
||||
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
|
||||
|
||||
@ -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,47 +21,84 @@ 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
|
||||
|
||||
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: }
|
||||
;;
|
||||
'signal: '*)
|
||||
signal=${line#signal: }
|
||||
signal=${signal% dBm}
|
||||
;;
|
||||
'Not connected.'*)
|
||||
ssid=''
|
||||
;;
|
||||
esac
|
||||
done < <(iw dev "$wifi_interface" link 2> /dev/null)
|
||||
|
||||
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
|
||||
[[ $line == *' inet '* ]] || continue
|
||||
read -r -a fields <<< "$line"
|
||||
for ((i = 0; i < ${#fields[@]}; i++)); do
|
||||
if [[ ${fields[i]} == inet && $((i + 1)) -lt ${#fields[@]} ]]; then
|
||||
ip_address=${fields[i + 1]%%/*}
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done < <(ip -o -4 addr show dev "$wifi_interface" scope global 2> /dev/null)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
ssid=''
|
||||
signal=''
|
||||
while IFS= read -r line; do
|
||||
case $line in
|
||||
'SSID: '*)
|
||||
ssid=${line#SSID: }
|
||||
;;
|
||||
'signal: '*)
|
||||
signal=${line#signal: }
|
||||
signal=${signal% dBm}
|
||||
;;
|
||||
'Not connected.'*)
|
||||
ssid=''
|
||||
;;
|
||||
esac
|
||||
done < <(iw dev "$wifi_interface" link 2> /dev/null)
|
||||
is_persist_mode() {
|
||||
[[ ${BLOCK_INTERVAL:-} == "persist" ]]
|
||||
}
|
||||
|
||||
if [[ -z $ssid ]]; then
|
||||
echo " down"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ip_address=''
|
||||
while IFS= read -r line; do
|
||||
[[ $line == *' inet '* ]] || continue
|
||||
read -r -a fields <<< "$line"
|
||||
for ((i = 0; i < ${#fields[@]}; i++)); do
|
||||
if [[ ${fields[i]} == inet && $((i + 1)) -lt ${#fields[@]} ]]; then
|
||||
ip_address=${fields[i + 1]%%/*}
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
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)"
|
||||
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
|
||||
|
||||
@ -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,71 +114,26 @@ 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
|
||||
local windows wid
|
||||
windows=$(xdotool search --name "$window_pattern" 2> /dev/null || true)
|
||||
for wid in $windows; do
|
||||
[[ -n $wid ]] || continue
|
||||
xdotool windowclose "$wid" 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)
|
||||
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)"
|
||||
xdotool windowclose "$wid" 2> /dev/null || true
|
||||
killed=true
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# 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
|
||||
return 0
|
||||
fi
|
||||
@ -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
|
||||
log_message "INSTANT KILL: Music services terminated"
|
||||
notify-send -u normal -t 2000 "🎵 Music killed" "Focus mode active" 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"
|
||||
fi
|
||||
fi
|
||||
next_enforcement_ts=$((current_ts + ENFORCEMENT_COOLDOWN))
|
||||
fi
|
||||
sleep "$FAST_CHECK_INTERVAL" # High-frequency check while focus app is active
|
||||
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"
|
||||
|
||||
@ -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}"
|
||||
|
||||
43
linux_configuration/scripts/digital_wellbeing/pacman/makepkg_capped.sh
Executable file
43
linux_configuration/scripts/digital_wellbeing/pacman/makepkg_capped.sh
Executable 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 "$@"
|
||||
8
linux_configuration/scripts/digital_wellbeing/pacman/mkpkg.sh
Executable file
8
linux_configuration/scripts/digital_wellbeing/pacman/mkpkg.sh
Executable 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 "$@"
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
1
linux_configuration/tests/__init__.py
Normal file
1
linux_configuration/tests/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for linux_configuration scripts and tooling."""
|
||||
@ -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")
|
||||
|
||||
146
linux_configuration/tests/test_i3blocks_persist_common.sh
Executable file
146
linux_configuration/tests/test_i3blocks_persist_common.sh
Executable 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'
|
||||
31
linux_configuration/tests/test_makepkg_capped.sh
Executable file
31
linux_configuration/tests/test_makepkg_capped.sh
Executable 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'
|
||||
@ -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"
|
||||
|
||||
41
linux_configuration/tests/test_usage_monitoring_installer_efficiency.sh
Executable file
41
linux_configuration/tests/test_usage_monitoring_installer_efficiency.sh
Executable 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'
|
||||
92
linux_configuration/tests/test_usage_report_pmon_names.py
Normal file
92
linux_configuration/tests/test_usage_report_pmon_names.py
Normal 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
|
||||
@ -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:
|
||||
|
||||
@ -85,6 +85,7 @@ PROTECTED_APP_IDS = {
|
||||
2252570,
|
||||
220200,
|
||||
3527290, # Peak
|
||||
1331550,
|
||||
}
|
||||
|
||||
STEAMAPPS_PATH = Path("~/.local/share/Steam/steamapps").expanduser()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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."""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user