monitor: split 449 -> 215 + 138 + 136, partitioned by probe kind

lib/monitor.sh was 199 lines over the cap. Its eleven _check_* probes
move into two siblings: monitor_checks_health.sh (format indicators,
battery, storage, the three enforcer daemons) and
monitor_checks_policy.sh (hosts blocklist, DNS chain, launcher,
companion app, boot persistence).

A single monitor_checks.sh was tried first and left at 265 lines — still
over, the net-zero outcome. Two libraries split the cluster at its own
seam and land both comfortably under.

The _MONITOR_* constants are partitioned by reader so no file assigns a
global it never reads. The five read only by the policy probes move into
that file; the ones monitor.sh reads itself or shares between both
children stay put. shellcheck is clean on each file standing alone.

Not a deployment concern: lib/monitor.sh is PC-side, consumed by
run_phone.sh, and deploy.sh never references lib/ — so neither copy list
changes.

Verified three ways: test_monitor.sh reports 9 passed / 0 failed, the
same count as before; verify_shell_split.sh confirms all 25 top-level
functions moved verbatim; and run_phone.sh --help exits 0, proving the
source chain resolves in the real consumer. Over-cap count 24 -> 23.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
This commit is contained in:
Krzysztof Rudnicki 2026-08-17 14:59:41 +02:00
parent c4bb3e93f6
commit 728301c9a4
5 changed files with 353 additions and 245 deletions

View File

@ -0,0 +1,17 @@
{
"title": "split lib/monitor.sh under the 250-line cap",
"objective": "Move monitor.sh's eleven _check_* probes into two sibling libraries so every resulting file is under 250 lines, with monitoring behaviour byte-identical.",
"acceptance_criteria": [
"test_monitor.sh reports exactly 9 passed, 0 failed — the same count as before the split",
"verify_shell_split.sh reports all 25 top-level functions moved verbatim",
"run_phone.sh --help exits 0, proving the source chain resolves in the real consumer",
"shellcheck is clean on each of the three files checked standing alone, with no suppressions",
"check_file_length.sh --all drops from 24 over-cap files to 23, with no new file over the cap"
],
"out_of_scope": [
"deploy.sh copy lists: lib/monitor.sh is PC-side and deploy.sh never references lib/",
"Any behavioural change to the probes themselves",
"Running the probes against a real device"
],
"verifier": "bash phone_focus_mode/lib/tests/test_monitor.sh + meta/scripts/verify_shell_split.sh + shellcheck"
}

View File

@ -0,0 +1,51 @@
{
"intent": "Bring phone_focus_mode/lib/monitor.sh under the 250-line cap by moving its health probes into two sibling libraries, without changing any monitoring behaviour.",
"scope": [
"phone_focus_mode/lib/monitor.sh — 449 lines, now 215",
"phone_focus_mode/lib/monitor_checks_health.sh — new, 138 lines",
"phone_focus_mode/lib/monitor_checks_policy.sh — new, 136 lines",
"Non-goal: deploy.sh copy lists. lib/monitor.sh is a PC-side library consumed by run_phone.sh; deploy.sh never references lib/ and does not push it to the phone."
],
"changes": [
"Moved the six device-health probes (_check_format_indicators, _check_battery, _check_storage, _check_daemon, _check_hosts_daemon, _check_launcher_daemon) into monitor_checks_health.sh.",
"Moved the five enforcement-integrity probes (_check_hosts_integrity, _check_dns, _check_launcher, _check_companion_app, _check_boot_persistence) into monitor_checks_policy.sh.",
"Partitioned the _MONITOR_* constants by reader so no file assigns a global it never reads (SC2034). The five read only by the policy probes moved into that file; _MONITOR_REMOTE_DIR, _MONITOR_HOSTS_TARGET, _MONITOR_HOSTS_CANDIDATES and the two launcher constants stay in monitor.sh, which reads them or shares them across both children.",
"Placed the two source lines after the constant block they depend on, using the already-computed _MONITOR_LIB_DIR rather than recomputing the directory per source line.",
"A single-library split was tried first and rejected: it left monitor_checks.sh at 265 lines, still over the cap, which is the net-zero outcome the brief warns about."
],
"verification": [
{
"command": "bash phone_focus_mode/lib/tests/test_monitor.sh",
"result": "pass",
"evidence": "9 passed, 0 failed — identical to the pre-split baseline captured before any edit. This is the discriminating check: the count matches exactly."
},
{
"command": "bash meta/scripts/verify_shell_split.sh HEAD phone_focus_mode/lib/monitor.sh phone_focus_mode/lib/monitor.sh phone_focus_mode/lib/monitor_checks_health.sh phone_focus_mode/lib/monitor_checks_policy.sh",
"result": "pass",
"evidence": "'IDENTICAL: all 25 top-level functions moved verbatim'. Note the old path must also be listed among the new paths for a partial split, since 14 functions stayed in monitor.sh; omitting it makes the tool report a false DIFFERENCE with an empty after-set."
},
{
"command": "bash phone_focus_mode/run_phone.sh --help",
"result": "pass",
"evidence": "Exit 0 with the full usage block, proving the source chain through both new libraries resolves in the real consumer, not just under the test harness."
},
{
"command": "shellcheck phone_focus_mode/lib/monitor.sh phone_focus_mode/lib/monitor_checks_health.sh phone_focus_mode/lib/monitor_checks_policy.sh",
"result": "pass",
"evidence": "All three clean, each checked standing alone (no -x), so the constant partition holds without a source directive."
},
{
"command": "bash meta/scripts/check_file_length.sh --all",
"result": "fail-as-expected",
"evidence": "24 files over the cap before, 23 after; monitor.sh at 215 and both new libraries at 138 and 136 are all under. No new file was pushed over."
}
],
"risks": [
"monitor.sh now sources two siblings before defining the helpers they call (_mon_check, _trim_output). This is safe because nothing executes until monitor_collect_snapshot runs, but a future top-level call in either library would break at source time.",
"The test covers 9 assertions against helper functions, none of which are the moved _check_* probes; those are device-dependent and unexercised here. The verbatim hash check is what covers them."
],
"rollback": [
"git revert the commit; monitor.sh returns to 449 lines and the two libraries disappear. run_phone.sh needs no change because it only ever sourced monitor.sh.",
"After rollback, re-run bash phone_focus_mode/lib/tests/test_monitor.sh and confirm 9 passed, then bash phone_focus_mode/run_phone.sh --help and confirm exit 0."
]
}

View File

@ -8,17 +8,23 @@ set -euo pipefail
_MONITOR_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly _MONITOR_LIB_DIR
# Constants read here or by both probe libraries. Those read by exactly one of
# them live in that file instead: a file must not assign a global it never
# reads (SC2034), and shellcheck runs without -x so each file stands alone.
readonly _MONITOR_REMOTE_DIR="/data/local/tmp/focus_mode"
readonly _MONITOR_HOSTS_CANONICAL="/data/local/tmp/focus_mode/hosts.canonical"
readonly _MONITOR_HOSTS_SHA_FILE="/data/local/tmp/focus_mode/hosts.sha256"
readonly _MONITOR_HOSTS_TARGET="/system/etc/hosts"
readonly _MONITOR_BOOT_SCRIPT="/data/adb/service.d/99-focus-mode.sh"
readonly _MONITOR_LAUNCHER_PACKAGE="com.qqlabs.minimalistlauncher"
readonly _MONITOR_LAUNCHER_ACTIVITY_FILE="/data/local/tmp/focus_mode/minimalist_launcher.activity"
readonly _MONITOR_COMPANION_PACKAGE="com.kuhy.focusstatus"
readonly _MONITOR_DNS_CHAIN="FOCUS_DNS_BLOCK"
readonly _MONITOR_HOSTS_CANDIDATES="/system/etc/hosts /etc/hosts /vendor/etc/hosts /system/system/etc/hosts"
# Sourced after the constants above, which the probes read. Both libraries call
# helpers defined below (_mon_check, _trim_output, _safe_adb_root_output); that
# is fine because nothing here runs until monitor_collect_snapshot is called.
# shellcheck source=monitor_checks_health.sh
. "${_MONITOR_LIB_DIR}/monitor_checks_health.sh"
# shellcheck source=monitor_checks_policy.sh
. "${_MONITOR_LIB_DIR}/monitor_checks_policy.sh"
_mon_escape_json() {
local escaped="$1"
@ -145,256 +151,16 @@ monitor_print_format_warning() {
_box "PHONE APPEARS TO HAVE BEEN WIPED" "${box_lines[@]}"
}
_check_format_indicators() {
local outfile="$1"
local -a missing_indicators=()
local status="ok"
local message="All format indicators are present"
mapfile -t missing_indicators < <(monitor_check_format_indicators)
if (( ${#missing_indicators[@]} >= FORMAT_DETECTION_MIN_MISSING )); then
status="fatal"
message="Missing ${#missing_indicators[@]} format indicators: ${missing_indicators[*]}"
elif (( ${#missing_indicators[@]} > 0 )); then
status="warn"
message="Missing ${#missing_indicators[@]} format indicators: ${missing_indicators[*]}"
fi
_mon_check "format_indicators" "${status}" "FORMAT_INDICATORS" "${message}" "false" >>"${outfile}"
}
_check_battery() {
local outfile="$1"
local level=""
local health=""
local temp=""
local status="ok"
local message=""
level="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/level:/{print \$2; exit}'")")"
health="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/health:/{print \$2; exit}'")")"
temp="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/temperature:/{print \$2; exit}'")")"
if [[ ! "${level}" =~ ^[0-9]+$ ]]; then
status="warn"
message="Battery level unavailable"
elif (( level < BATTERY_WARN_BELOW )); then
status="warn"
message="Battery low: ${level}% (threshold ${BATTERY_WARN_BELOW}%)"
else
message="Battery level ${level}%, health ${health:-unknown}, temp ${temp:-unknown}"
fi
_mon_check "battery" "${status}" "dumpsys battery" "${message}" "false" >>"${outfile}"
}
_check_storage() {
local outfile="$1"
local free_kb=""
local free_mb=0
local status="ok"
local message=""
free_kb="$(_trim_output "$(_safe_adb_root_output "df /sdcard 2>/dev/null | awk 'NR==2{print \$4; exit}'")")"
if [[ ! "${free_kb}" =~ ^[0-9]+$ ]]; then
free_kb="$(_trim_output "$(_safe_adb_root_output "df /storage/emulated/0 2>/dev/null | awk 'NR==2{print \$4; exit}'")")"
fi
if [[ "${free_kb}" =~ ^[0-9]+$ ]]; then
free_mb=$((free_kb / 1024))
if (( free_mb < STORAGE_WARN_BELOW_MB )); then
status="warn"
message="Low storage: ${free_mb} MB free (threshold ${STORAGE_WARN_BELOW_MB} MB)"
else
message="Free storage: ${free_mb} MB"
fi
else
status="warn"
message="Free storage unavailable"
fi
_mon_check "storage" "${status}" "df /sdcard" "${message}" "false" >>"${outfile}"
}
_check_daemon() {
local daemon_name="$1"
local script_name="$2"
local pidfile="$3"
local outfile="$4"
local pid=""
local pgrep_pid=""
pid="$(_monitor_read_pidfile "${pidfile}")"
if [[ "${pid}" =~ ^[0-9]+$ ]] && _monitor_pid_matches_script "${pid}" "${script_name}"; then
_mon_check "${daemon_name}" "ok" "${pidfile}" "${daemon_name} running (PID ${pid})" "false" >>"${outfile}"
return
fi
pgrep_pid="$(_trim_output "$(_safe_adb_root_output "pgrep -f '${script_name}' 2>/dev/null | head -1")")"
if [[ "${pgrep_pid}" =~ ^[0-9]+$ ]] && adb_root_shell "kill -0 ${pgrep_pid} >/dev/null 2>&1" >/dev/null 2>&1; then
_mon_check "${daemon_name}" "ok" "pgrep -f ${script_name}" "${daemon_name} running (PID ${pgrep_pid})" "false" >>"${outfile}"
return
fi
_mon_check "${daemon_name}" "error" "${pidfile}" "${daemon_name} is NOT running" "true" >>"${outfile}"
}
_check_hosts_daemon() {
local outfile="$1"
local resolved_target=""
resolved_target="$(_monitor_resolve_hosts_target)"
if [[ -z "${resolved_target}" ]]; then
_mon_check "hosts_enforcer" "warn" "hosts target probe" \
"No hosts target file exists on this ROM; hosts daemon check skipped" "false" >>"${outfile}"
return
fi
_check_daemon "hosts_enforcer" "hosts_enforcer.sh" "${_MONITOR_REMOTE_DIR}/hosts_enforcer.pid" "${outfile}"
}
_check_launcher_daemon() {
local outfile="$1"
local has_snapshot="no"
local launcher_installed="no"
if adb_root_shell "test -s '${_MONITOR_LAUNCHER_ACTIVITY_FILE}'" >/dev/null 2>&1; then
has_snapshot="yes"
fi
if adb_root_shell "pm path '${_MONITOR_LAUNCHER_PACKAGE}' >/dev/null 2>&1" >/dev/null 2>&1; then
launcher_installed="yes"
fi
if [[ "${has_snapshot}" == "no" && "${launcher_installed}" == "no" ]]; then
_mon_check "launcher_enforcer" "warn" "launcher optional probe" \
"Launcher enforcer check skipped (launcher not configured yet)" "false" >>"${outfile}"
return
fi
_check_daemon "launcher_enforcer" "launcher_enforcer.sh" "${_MONITOR_REMOTE_DIR}/launcher_enforcer.pid" "${outfile}"
}
_check_hosts_integrity() {
local outfile="$1"
local hosts_target=""
local expected_hash=""
local actual_hash=""
hosts_target="$(_monitor_resolve_hosts_target)"
if [[ -z "${hosts_target}" ]]; then
_mon_check "hosts_integrity" "warn" "hosts target probe" \
"No hosts file target exists on this ROM; hosts integrity check skipped" "false" >>"${outfile}"
return
fi
if ! adb_root_shell "test -f ${_MONITOR_HOSTS_CANONICAL}" >/dev/null 2>&1; then
_mon_check "hosts_integrity" "fatal" "${_MONITOR_HOSTS_CANONICAL}" \
"Canonical hosts file missing at ${_MONITOR_HOSTS_CANONICAL}" "true" >>"${outfile}"
return
fi
expected_hash="$(_trim_output "$(_safe_adb_root_output "cat ${_MONITOR_HOSTS_SHA_FILE} 2>/dev/null")")"
actual_hash="$(_trim_output "$(_safe_adb_root_output "sha256sum ${hosts_target} 2>/dev/null | awk '{print \$1}'")")"
if [[ -z "${expected_hash}" || -z "${actual_hash}" ]]; then
_mon_check "hosts_integrity" "error" "${hosts_target}" \
"Could not read hosts integrity hashes" "true" >>"${outfile}"
elif [[ "${expected_hash}" == "${actual_hash}" ]]; then
_mon_check "hosts_integrity" "ok" "${hosts_target}" \
"Hosts file matches canonical (${actual_hash:0:12}…)" "false" >>"${outfile}"
else
_mon_check "hosts_integrity" "error" "${hosts_target}" \
"Hosts mismatch: active ${actual_hash:0:12}… != expected ${expected_hash:0:12}…" "true" >>"${outfile}"
fi
}
_check_dns() {
local outfile="$1"
local private_dns_mode=""
local chain_present="no"
local status="ok"
local message=""
private_dns_mode="$(_trim_output "$(_safe_adb_root_output "settings get global private_dns_mode 2>/dev/null")")"
if adb_root_shell "iptables -L ${_MONITOR_DNS_CHAIN} >/dev/null 2>&1 && ip6tables -L ${_MONITOR_DNS_CHAIN} >/dev/null 2>&1" >/dev/null 2>&1; then
chain_present="yes"
fi
if [[ "${private_dns_mode}" == "off" || "${private_dns_mode}" == "null" || -z "${private_dns_mode}" ]]; then
if [[ "${chain_present}" == "yes" ]]; then
message="Private DNS disabled and DNS firewall chains present"
else
status="error"
message="Private DNS disabled, but DNS firewall chains are missing"
fi
else
status="error"
message="Private DNS is enabled (mode=${private_dns_mode})"
fi
_mon_check "dns_enforcement" "${status}" "settings get global private_dns_mode" "${message}" "true" >>"${outfile}"
}
_check_launcher() {
local outfile="$1"
local desired_activity=""
local actual_activity=""
local has_snapshot="no"
if adb_root_shell "test -s '${_MONITOR_LAUNCHER_ACTIVITY_FILE}'" >/dev/null 2>&1; then
has_snapshot="yes"
fi
if ! adb_root_shell "pm path '${_MONITOR_LAUNCHER_PACKAGE}' >/dev/null 2>&1" >/dev/null 2>&1; then
if [[ "${has_snapshot}" == "yes" ]]; then
_mon_check "launcher_state" "fatal" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher is not installed but snapshot metadata exists" "true" >>"${outfile}"
else
_mon_check "launcher_state" "warn" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher is not installed (optional until snapshot is configured)" "false" >>"${outfile}"
fi
return
fi
desired_activity="$(_trim_output "$(_safe_adb_root_output "cat ${_MONITOR_LAUNCHER_ACTIVITY_FILE} 2>/dev/null")")"
actual_activity="$(_trim_output "$(_safe_adb_root_output "cmd package resolve-activity --brief -c android.intent.category.HOME -a android.intent.action.MAIN 2>/dev/null | awk 'NR==2{print}'")")"
if [[ -z "${desired_activity}" ]]; then
_mon_check "launcher_state" "warn" "cat ${_MONITOR_LAUNCHER_ACTIVITY_FILE}" \
"Launcher snapshot metadata is missing or empty" "true" >>"${outfile}"
elif [[ -n "${actual_activity}" && "${desired_activity}" != "${actual_activity}" ]]; then
_mon_check "launcher_state" "error" "cmd package resolve-activity" \
"Launcher default mismatch: expected ${desired_activity}, got ${actual_activity}" "true" >>"${outfile}"
else
_mon_check "launcher_state" "ok" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher installed and HOME activity aligned" "false" >>"${outfile}"
fi
}
_check_companion_app() {
local outfile="$1"
if adb_root_shell "pm list packages -e '${_MONITOR_COMPANION_PACKAGE}' 2>/dev/null | grep -q '${_MONITOR_COMPANION_PACKAGE}'" >/dev/null 2>&1; then
_mon_check "companion_app" "ok" "pm list packages -e ${_MONITOR_COMPANION_PACKAGE}" \
"Focus companion app is installed" "false" >>"${outfile}"
else
_mon_check "companion_app" "warn" "pm list packages -e ${_MONITOR_COMPANION_PACKAGE}" \
"Focus companion app is missing" "true" >>"${outfile}"
fi
}
_check_boot_persistence() {
local outfile="$1"
if adb_root_shell "test -x ${_MONITOR_BOOT_SCRIPT}" >/dev/null 2>&1; then
_mon_check "boot_persistence" "ok" "test -x ${_MONITOR_BOOT_SCRIPT}" \
"Magisk boot script present and executable" "false" >>"${outfile}"
else
_mon_check "boot_persistence" "fatal" "test -x ${_MONITOR_BOOT_SCRIPT}" \
"Magisk boot script missing or not executable" "true" >>"${outfile}"
fi
}
monitor_collect_snapshot() {
local snapshot_dir="$1"

View File

@ -0,0 +1,138 @@
#!/usr/bin/env bash
# lib/monitor_checks_health.sh — device-health probes behind monitor.sh:
# format indicators, battery, storage, and the three enforcer daemons.
# Each _check_* appends one JSON object to the checks file it is given.
#
# Sourced by monitor.sh, which defines every _MONITOR_* constant these read
# and the _mon_check/_trim_output/_monitor_read_pidfile helpers they call.
# Nothing is assigned here, so there is no constant to keep in step.
_check_format_indicators() {
local outfile="$1"
local -a missing_indicators=()
local status="ok"
local message="All format indicators are present"
mapfile -t missing_indicators < <(monitor_check_format_indicators)
if (( ${#missing_indicators[@]} >= FORMAT_DETECTION_MIN_MISSING )); then
status="fatal"
message="Missing ${#missing_indicators[@]} format indicators: ${missing_indicators[*]}"
elif (( ${#missing_indicators[@]} > 0 )); then
status="warn"
message="Missing ${#missing_indicators[@]} format indicators: ${missing_indicators[*]}"
fi
_mon_check "format_indicators" "${status}" "FORMAT_INDICATORS" "${message}" "false" >>"${outfile}"
}
_check_battery() {
local outfile="$1"
local level=""
local health=""
local temp=""
local status="ok"
local message=""
level="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/level:/{print \$2; exit}'")")"
health="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/health:/{print \$2; exit}'")")"
temp="$(_trim_output "$(_safe_adb_root_output "dumpsys battery | awk -F': ' '/temperature:/{print \$2; exit}'")")"
if [[ ! "${level}" =~ ^[0-9]+$ ]]; then
status="warn"
message="Battery level unavailable"
elif (( level < BATTERY_WARN_BELOW )); then
status="warn"
message="Battery low: ${level}% (threshold ${BATTERY_WARN_BELOW}%)"
else
message="Battery level ${level}%, health ${health:-unknown}, temp ${temp:-unknown}"
fi
_mon_check "battery" "${status}" "dumpsys battery" "${message}" "false" >>"${outfile}"
}
_check_storage() {
local outfile="$1"
local free_kb=""
local free_mb=0
local status="ok"
local message=""
free_kb="$(_trim_output "$(_safe_adb_root_output "df /sdcard 2>/dev/null | awk 'NR==2{print \$4; exit}'")")"
if [[ ! "${free_kb}" =~ ^[0-9]+$ ]]; then
free_kb="$(_trim_output "$(_safe_adb_root_output "df /storage/emulated/0 2>/dev/null | awk 'NR==2{print \$4; exit}'")")"
fi
if [[ "${free_kb}" =~ ^[0-9]+$ ]]; then
free_mb=$((free_kb / 1024))
if (( free_mb < STORAGE_WARN_BELOW_MB )); then
status="warn"
message="Low storage: ${free_mb} MB free (threshold ${STORAGE_WARN_BELOW_MB} MB)"
else
message="Free storage: ${free_mb} MB"
fi
else
status="warn"
message="Free storage unavailable"
fi
_mon_check "storage" "${status}" "df /sdcard" "${message}" "false" >>"${outfile}"
}
_check_daemon() {
local daemon_name="$1"
local script_name="$2"
local pidfile="$3"
local outfile="$4"
local pid=""
local pgrep_pid=""
pid="$(_monitor_read_pidfile "${pidfile}")"
if [[ "${pid}" =~ ^[0-9]+$ ]] && _monitor_pid_matches_script "${pid}" "${script_name}"; then
_mon_check "${daemon_name}" "ok" "${pidfile}" "${daemon_name} running (PID ${pid})" "false" >>"${outfile}"
return
fi
pgrep_pid="$(_trim_output "$(_safe_adb_root_output "pgrep -f '${script_name}' 2>/dev/null | head -1")")"
if [[ "${pgrep_pid}" =~ ^[0-9]+$ ]] && adb_root_shell "kill -0 ${pgrep_pid} >/dev/null 2>&1" >/dev/null 2>&1; then
_mon_check "${daemon_name}" "ok" "pgrep -f ${script_name}" "${daemon_name} running (PID ${pgrep_pid})" "false" >>"${outfile}"
return
fi
_mon_check "${daemon_name}" "error" "${pidfile}" "${daemon_name} is NOT running" "true" >>"${outfile}"
}
_check_hosts_daemon() {
local outfile="$1"
local resolved_target=""
resolved_target="$(_monitor_resolve_hosts_target)"
if [[ -z "${resolved_target}" ]]; then
_mon_check "hosts_enforcer" "warn" "hosts target probe" \
"No hosts target file exists on this ROM; hosts daemon check skipped" "false" >>"${outfile}"
return
fi
_check_daemon "hosts_enforcer" "hosts_enforcer.sh" "${_MONITOR_REMOTE_DIR}/hosts_enforcer.pid" "${outfile}"
}
_check_launcher_daemon() {
local outfile="$1"
local has_snapshot="no"
local launcher_installed="no"
if adb_root_shell "test -s '${_MONITOR_LAUNCHER_ACTIVITY_FILE}'" >/dev/null 2>&1; then
has_snapshot="yes"
fi
if adb_root_shell "pm path '${_MONITOR_LAUNCHER_PACKAGE}' >/dev/null 2>&1" >/dev/null 2>&1; then
launcher_installed="yes"
fi
if [[ "${has_snapshot}" == "no" && "${launcher_installed}" == "no" ]]; then
_mon_check "launcher_enforcer" "warn" "launcher optional probe" \
"Launcher enforcer check skipped (launcher not configured yet)" "false" >>"${outfile}"
return
fi
_check_daemon "launcher_enforcer" "launcher_enforcer.sh" "${_MONITOR_REMOTE_DIR}/launcher_enforcer.pid" "${outfile}"
}

View File

@ -0,0 +1,136 @@
#!/usr/bin/env bash
# lib/monitor_checks_policy.sh — enforcement-integrity probes behind monitor.sh:
# the hosts blocklist, DNS chain, launcher, companion app and boot persistence.
# Each _check_* appends one JSON object to the checks file it is given.
#
# Sourced by monitor.sh, which defines _MONITOR_LAUNCHER_PACKAGE and
# _MONITOR_LAUNCHER_ACTIVITY_FILE (shared with the health probes) plus the
# _mon_check/_trim_output/_safe_adb_root_output helpers these call.
# The constants below are read only here, so they are assigned only here.
readonly _MONITOR_HOSTS_CANONICAL="/data/local/tmp/focus_mode/hosts.canonical"
readonly _MONITOR_HOSTS_SHA_FILE="/data/local/tmp/focus_mode/hosts.sha256"
readonly _MONITOR_BOOT_SCRIPT="/data/adb/service.d/99-focus-mode.sh"
readonly _MONITOR_COMPANION_PACKAGE="com.kuhy.focusstatus"
readonly _MONITOR_DNS_CHAIN="FOCUS_DNS_BLOCK"
_check_hosts_integrity() {
local outfile="$1"
local hosts_target=""
local expected_hash=""
local actual_hash=""
hosts_target="$(_monitor_resolve_hosts_target)"
if [[ -z "${hosts_target}" ]]; then
_mon_check "hosts_integrity" "warn" "hosts target probe" \
"No hosts file target exists on this ROM; hosts integrity check skipped" "false" >>"${outfile}"
return
fi
if ! adb_root_shell "test -f ${_MONITOR_HOSTS_CANONICAL}" >/dev/null 2>&1; then
_mon_check "hosts_integrity" "fatal" "${_MONITOR_HOSTS_CANONICAL}" \
"Canonical hosts file missing at ${_MONITOR_HOSTS_CANONICAL}" "true" >>"${outfile}"
return
fi
expected_hash="$(_trim_output "$(_safe_adb_root_output "cat ${_MONITOR_HOSTS_SHA_FILE} 2>/dev/null")")"
actual_hash="$(_trim_output "$(_safe_adb_root_output "sha256sum ${hosts_target} 2>/dev/null | awk '{print \$1}'")")"
if [[ -z "${expected_hash}" || -z "${actual_hash}" ]]; then
_mon_check "hosts_integrity" "error" "${hosts_target}" \
"Could not read hosts integrity hashes" "true" >>"${outfile}"
elif [[ "${expected_hash}" == "${actual_hash}" ]]; then
_mon_check "hosts_integrity" "ok" "${hosts_target}" \
"Hosts file matches canonical (${actual_hash:0:12}…)" "false" >>"${outfile}"
else
_mon_check "hosts_integrity" "error" "${hosts_target}" \
"Hosts mismatch: active ${actual_hash:0:12}… != expected ${expected_hash:0:12}…" "true" >>"${outfile}"
fi
}
_check_dns() {
local outfile="$1"
local private_dns_mode=""
local chain_present="no"
local status="ok"
local message=""
private_dns_mode="$(_trim_output "$(_safe_adb_root_output "settings get global private_dns_mode 2>/dev/null")")"
if adb_root_shell "iptables -L ${_MONITOR_DNS_CHAIN} >/dev/null 2>&1 && ip6tables -L ${_MONITOR_DNS_CHAIN} >/dev/null 2>&1" >/dev/null 2>&1; then
chain_present="yes"
fi
if [[ "${private_dns_mode}" == "off" || "${private_dns_mode}" == "null" || -z "${private_dns_mode}" ]]; then
if [[ "${chain_present}" == "yes" ]]; then
message="Private DNS disabled and DNS firewall chains present"
else
status="error"
message="Private DNS disabled, but DNS firewall chains are missing"
fi
else
status="error"
message="Private DNS is enabled (mode=${private_dns_mode})"
fi
_mon_check "dns_enforcement" "${status}" "settings get global private_dns_mode" "${message}" "true" >>"${outfile}"
}
_check_launcher() {
local outfile="$1"
local desired_activity=""
local actual_activity=""
local has_snapshot="no"
if adb_root_shell "test -s '${_MONITOR_LAUNCHER_ACTIVITY_FILE}'" >/dev/null 2>&1; then
has_snapshot="yes"
fi
if ! adb_root_shell "pm path '${_MONITOR_LAUNCHER_PACKAGE}' >/dev/null 2>&1" >/dev/null 2>&1; then
if [[ "${has_snapshot}" == "yes" ]]; then
_mon_check "launcher_state" "fatal" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher is not installed but snapshot metadata exists" "true" >>"${outfile}"
else
_mon_check "launcher_state" "warn" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher is not installed (optional until snapshot is configured)" "false" >>"${outfile}"
fi
return
fi
desired_activity="$(_trim_output "$(_safe_adb_root_output "cat ${_MONITOR_LAUNCHER_ACTIVITY_FILE} 2>/dev/null")")"
actual_activity="$(_trim_output "$(_safe_adb_root_output "cmd package resolve-activity --brief -c android.intent.category.HOME -a android.intent.action.MAIN 2>/dev/null | awk 'NR==2{print}'")")"
if [[ -z "${desired_activity}" ]]; then
_mon_check "launcher_state" "warn" "cat ${_MONITOR_LAUNCHER_ACTIVITY_FILE}" \
"Launcher snapshot metadata is missing or empty" "true" >>"${outfile}"
elif [[ -n "${actual_activity}" && "${desired_activity}" != "${actual_activity}" ]]; then
_mon_check "launcher_state" "error" "cmd package resolve-activity" \
"Launcher default mismatch: expected ${desired_activity}, got ${actual_activity}" "true" >>"${outfile}"
else
_mon_check "launcher_state" "ok" "pm path ${_MONITOR_LAUNCHER_PACKAGE}" \
"Minimalist launcher installed and HOME activity aligned" "false" >>"${outfile}"
fi
}
_check_companion_app() {
local outfile="$1"
if adb_root_shell "pm list packages -e '${_MONITOR_COMPANION_PACKAGE}' 2>/dev/null | grep -q '${_MONITOR_COMPANION_PACKAGE}'" >/dev/null 2>&1; then
_mon_check "companion_app" "ok" "pm list packages -e ${_MONITOR_COMPANION_PACKAGE}" \
"Focus companion app is installed" "false" >>"${outfile}"
else
_mon_check "companion_app" "warn" "pm list packages -e ${_MONITOR_COMPANION_PACKAGE}" \
"Focus companion app is missing" "true" >>"${outfile}"
fi
}
_check_boot_persistence() {
local outfile="$1"
if adb_root_shell "test -x ${_MONITOR_BOOT_SCRIPT}" >/dev/null 2>&1; then
_mon_check "boot_persistence" "ok" "test -x ${_MONITOR_BOOT_SCRIPT}" \
"Magisk boot script present and executable" "false" >>"${outfile}"
else
_mon_check "boot_persistence" "fatal" "test -x ${_MONITOR_BOOT_SCRIPT}" \
"Magisk boot script missing or not executable" "true" >>"${outfile}"
fi
}