Extract android_guardian; heavy_job_lock goes to its installer

github.com/kuhyx/android-guardian, cloned to ~/android-guardian

periodic_background/utils/ held two unrelated things: the Magisk module
and heavy_job_lock.sh. Only the module was a unit, so the directory is
split rather than moved.

heavy_job_lock.sh goes to digital_wellbeing/, NOT to ~/utils. Measured
rather than assumed: of its consumers only install_pacman_wrapper.sh
reads the repo copy, while pacman_wrapper.sh, makepkg_capped.sh and
meta/scripts/run_with_heavy_lock.sh all read the installed
/usr/local/bin copy. A library belongs with whoever installs it, and
exporting it would have added a cross-repo edge for one caller.

Fixed a bug that predates the extraction: update_android_hosts.sh set
GUARDIAN_MODULE_DIR to "$SCRIPT_DIR/android_guardian", a sibling that
never existed at that path -- packaging could not have worked from this
checkout at all. It now reads ${ANDROID_GUARDIAN_DIR:-~/android-guardian}
and require_guardian_module() gates build_module(), the single funnel
every packaging path goes through, so a missing checkout names the clone
command instead of failing inside a cp four helpers deep.

Verified both directions: with no checkout the guard fires with
instructions; with one, the module builds -- 182331 blocked domains, a
1.1 MB zip. The subsequent adb push failed on device-side storage, which
this commit does not touch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
This commit is contained in:
Krzysztof Rudnicki 2026-08-23 18:00:58 +02:00
parent a1f8044a4a
commit fe8629e104
15 changed files with 90 additions and 622 deletions

View File

@ -26,8 +26,6 @@ jobs:
run: |
set -euo pipefail
tests=(
test_android_guardian_post_fs_data.sh
test_android_guardian_service.sh
test_i3blocks_persist_common.sh
test_music_parallelism.sh
test_setup_dns_blocker.sh

View File

@ -30,7 +30,9 @@ Extracted to their own repos: [`steam-backlog-enforcer`](https://github.com/kuhy
[`focus-owner`](https://github.com/kuhyx/focus-owner),
[`mtk-root`](https://github.com/kuhyx/mtk-root),
[`system-maintenance`](https://github.com/kuhyx/system-maintenance) (lives at
`~/system-maintenance`).
`~/system-maintenance`),
[`android-guardian`](https://github.com/kuhyx/android-guardian) (lives at
`~/android-guardian`).
Archived work: [`testsAndMisc-archive`](https://github.com/kuhyx/testsAndMisc-archive).
## Adding a Flutter app

View File

@ -0,0 +1,20 @@
{
"title": "Extract android_guardian and settle the heavy_job_lock seam",
"objective": "Move the Magisk module to its own public repo. The directory it lived in also held heavy_job_lock.sh, a shared lock library that is NOT part of the module, so this extraction has to split the directory rather than move it wholesale. It also inherits a packaging script whose module path was already broken before the extraction.",
"acceptance_criteria": [
"The module and both its test suites are in the new public repo, tests passing from a clean clone, shellcheck exiting 0 with no severity filter.",
"heavy_job_lock.sh lands where its installer is, justified by measuring which consumers read the repo copy versus the installed /usr/local/bin copy - not by the ad-hoc '>2 consumers' rule.",
"install_pacman_wrapper.sh's relative path to heavy_job_lock.sh resolves after the move.",
"update_android_hosts.sh points at a real location, and a missing checkout fails with an actionable message at a single funnel rather than a bare cp error deep in a helper.",
"The module still builds end-to-end from the new checkout.",
"shell-tests.yml names no removed suite; the coverage allowlist names no removed path.",
"periodic_background/utils/ is fully emptied, not left as a one-file directory.",
"pre-commit passes without --no-verify."
],
"out_of_scope": [
"Moving heavy_job_lock.sh to ~/utils: the measurement does not support it, and every consumer but the installer already reads the installed copy.",
"The adb push half of update_android_hosts.sh, which needs a device in a writable state.",
"digital_wellbeing itself, which is the next and last extraction."
],
"verifier": "bash tests/*.sh in the new repo passes; shellcheck exits 0 there; ANDROID_GUARDIAN_DIR=/nonexistent produces the guard message; a real run builds the zip (182331 domains); shellcheck and check_shell_coverage.sh --all clean here; check_directory_depth.sh --all continues to fall."
}

View File

@ -0,0 +1,48 @@
{
"intent": "Extract the android_guardian Magisk module into its own public repo, settle where heavy_job_lock.sh belongs, and fix a pre-existing broken path in the packaging script rather than carrying it forward.",
"scope": [
"periodic_background/utils/android_guardian/ (5 files) -> github.com/kuhyx/android-guardian as module/, cloned to ~/android-guardian",
"Its two test suites in linux_configuration/tests/ moved with it",
"heavy_job_lock.sh moved to digital_wellbeing/, its only installer - NOT to ~/utils",
"update_android_hosts.sh + lib/android_module.sh repointed and guarded",
"shell-tests.yml, shell-coverage-allowlist.txt cleaned",
"periodic_background/utils/ is now empty and removed"
],
"changes": [
"Extracted the module and both suites in one filter-repo pass (4 commits), renaming the module directory to module/ and fixing the test paths that named the old monorepo location.",
"Moved heavy_job_lock.sh into digital_wellbeing/ instead of ~/utils. Measured: of its consumers, only install_pacman_wrapper.sh reads the repo copy; pacman_wrapper.sh, makepkg_capped.sh and meta/scripts/run_with_heavy_lock.sh all read the installed /usr/local/bin path. It belongs with its installer.",
"Fixed a pre-existing bug: update_android_hosts.sh set GUARDIAN_MODULE_DIR to $SCRIPT_DIR/android_guardian, a sibling directory that never existed at that path, so packaging could not have worked from this checkout. It now reads ${ANDROID_GUARDIAN_DIR:-$HOME/android-guardian}/module.",
"Added require_guardian_module() and called it from build_module(), the single funnel every packaging path goes through, so a missing checkout fails with an actionable message instead of a bare 'cp: cannot stat' four helpers deep."
],
"verification": [
{
"command": "bash tests/*.sh (extracted repo)",
"result": "pass",
"evidence": "Both suites pass after the path rewrite; shellcheck exits 0 over every script with .shellcheckrc carried across."
},
{
"command": "ANDROID_GUARDIAN_DIR=/nonexistent bash update_android_hosts.sh install",
"result": "pass",
"evidence": "Stops at the guard with the clone command and the override variable named, rather than failing inside cp."
},
{
"command": "bash update_android_hosts.sh install (with ~/android-guardian cloned)",
"result": "pass",
"evidence": "Module built: 182331 blocked domains, a 1149321-byte zip. It then failed at `adb push` with 'remote couldn't create file' - a device-side storage condition, not a path problem, and the build half is what this change touches."
},
{
"command": "shellcheck on both edited scripts; check_shell_coverage.sh --all",
"result": "pass",
"evidence": "Both clean, exit 0."
}
],
"risks": [
"The adb push step was not verified end-to-end: the device refused to create the file on /sdcard. That path is unchanged by this commit, but it does mean the full install flow is unproven today.",
"update_android_hosts.sh now depends on a sibling checkout. It fails loudly with instructions rather than silently, which is the tradeoff taken.",
"heavy_job_lock.sh now sits inside digital_wellbeing, so it will move again when that subsystem is extracted; install_pacman_wrapper.sh's relative path was updated to match and stays correct through that move."
],
"rollback": [
"git revert to restore the module, both tests and heavy_job_lock.sh's old location.",
"Re-run check_shell_coverage.sh --all and the two suites to confirm the restored state."
]
}

View File

@ -41,7 +41,7 @@ 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"
HEAVY_LOCK_SOURCE="$(dirname "$0")/../../utils/heavy_job_lock.sh"
HEAVY_LOCK_SOURCE="$(dirname "$0")/../heavy_job_lock.sh"
INSTALL_DIR="/usr/local/bin"
WRAPPER_DEST="${INSTALL_DIR}/pacman_wrapper"
LOCK_LIB_DEST="${INSTALL_DIR}/pacman_lock_lib.sh"

View File

@ -1,206 +0,0 @@
# Android Guardian - Blocked Apps List
# One package name per line
# Lines starting with # are comments
# ===== Food Delivery Apps =====
# Uber Eats
com.ubercab.eats
com.ubercab
# Glovo
com.glovo
com.glovo.courier
# Wolt
com.wolt.android
# Bolt Food / Bolt
com.bolt.deliveryclient
ee.mtakso.client
ee.mtakso.food
ee.mtakso
# Pyszne.pl / Takeaway
com.takeaway.android
com.pyszne.pl
com.takeaway.android.pl
# Jush (Polish quick delivery)
pl.jush.app
pl.jush.android
# Delio (Polish delivery)
pl.delio.app
pl.delio.android
com.delio.app
# Lisek (Polish quick delivery)
pl.lisek.app
com.lisek.app
# Stava (Polish delivery)
pl.stava.app
# Barbora (Polish grocery delivery)
pl.barbora.app
com.barbora.pl
# Frisco (Polish grocery delivery)
pl.frisco.app
com.frisco.android
# Szama (Polish food delivery)
pl.szama.app
# Biedronka online grocery
pl.biedronka.app
com.jeronimo.biedronka
# DoorDash
com.dd.doordash
# Grubhub
com.grubhub.android
# Deliveroo
com.deliveroo.orderapp
# Just Eat
com.justeat.app.uk
com.justeat.android
# Postmates
com.postmates.android
# Seamless
com.seamless.consumer
# Foodpanda
com.global.foodpanda.android
# Getir (quick delivery)
com.getir
com.getir.express
# Flink
com.flink.android
com.flink.consumer
# GoPuff
com.gopuff.android
# Instacart
com.instacart.client
# Gorillas
com.gorillas.consumer
# ===== Browsers (to prevent bypassing blocks) =====
# Firefox (allowed - hosts blocking is sufficient)
# org.mozilla.firefox
# org.mozilla.firefox_beta
# org.mozilla.fenix
# Chrome (comment out if needed for some functionality)
# com.android.chrome
# Opera
com.opera.browser
com.opera.mini.native
com.opera.gx
# Brave
com.brave.browser
# Samsung Internet
com.sec.android.app.sbrowser
# Microsoft Edge
com.microsoft.emmx
# DuckDuckGo
com.duckduckgo.mobile.android
# Kiwi Browser
com.kiwibrowser.browser
# Vivaldi
com.vivaldi.browser
# UC Browser
com.UCMobile.intl
# Tor Browser
org.torproject.torbrowser
# ===== YouTube Apps =====
com.google.android.youtube
com.vanced.android.youtube
app.revanced.android.youtube
app.revanced.android.apps.youtube.music
# ===== Alternative YouTube Frontend Apps =====
# NewPipe
org.schabi.newpipe
org.schabi.newpipelegacy
# LibreTube
com.github.libretube
# SkyTube
free.rm.skytube.oss
free.rm.skytube.extra
# Clipious (Invidious client)
com.github.lamarios.clipious
# Hyperion (YouTube client)
us.spotco.hyperion
# GrayJay
com.futo.platformplayer
# PipePipe
InfinityLoop1309.NewPipeEnhanced
# Materialious
com.materialious.app
# VueTube
app.vuetube.app
# ===== Fast Food Apps =====
# McDonald's
com.mcdonalds.app
com.mcdonalds.mobileapp
# KFC
com.yum.kfcpolska
com.kfc.app
# Burger King
com.emn8.mobilem8.nativeapp.bk
com.burgerking
# Pizza Hut
com.pizzahut.app
# Domino's
com.dominospizza
# Subway
com.subway.mobile
# ===== Other Distracting Apps =====
# TikTok
com.zhiliaoapp.musically
com.ss.android.ugc.trill
# Instagram (optional - comment out if needed)
# com.instagram.android
# Facebook (optional)
# com.facebook.katana
# Twitter/X
# com.twitter.android

View File

@ -1,6 +0,0 @@
id=android_guardian
name=Android Guardian (Hosts + App Blocker)
version=1.1
versionCode=2
author=linux-configuration
description=Persistent hosts blocking and app installation guard. Protected by watchdog - cannot be disabled from Magisk UI. Only controllable via ADB.

View File

@ -1,101 +0,0 @@
#!/system/bin/sh
# Runs early in boot - set up hosts file and start watchdog
# MODDIR is set by Magisk and points to this module's directory
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
WATCHDOG_SCRIPT="${ANDROID_GUARDIAN_WATCHDOG_SCRIPT:-$GUARDIAN_DIR/watchdog.sh}"
LOG_FILE="$GUARDIAN_DIR/guardian.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] post-fs-data: $*" >>"$LOG_FILE"
}
write_watchdog_script() {
cat >"$WATCHDOG_SCRIPT" <<'WATCHDOG'
#!/system/bin/sh
# Secondary watchdog - runs independently of module state
# Even if module is "disabled" in Magisk UI, this keeps running and undoes it
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
MODULE_DIR="${ANDROID_GUARDIAN_MODULE_DIR:-/data/adb/modules/android_guardian}"
LOG_FILE="$GUARDIAN_DIR/watchdog.log"
CONTROL_FILE="$GUARDIAN_DIR/control"
HOSTS_BACKUP="$GUARDIAN_DIR/hosts.backup"
MODULE_HOSTS="$MODULE_DIR/system/etc/hosts"
LOOP_SLEEP_SECONDS=3
HOSTS_CHECK_EVERY_TICKS=10
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
}
is_enabled() {
if [ ! -f "$CONTROL_FILE" ]; then
return 1
fi
IFS= read -r guardian_state < "$CONTROL_FILE" || guardian_state=""
[ "$guardian_state" = "ENABLED" ]
}
protect_module_flags() {
if [ -f "$MODULE_DIR/disable" ]; then
log "ALERT: Module disable detected via Magisk UI - removing disable flag"
rm -f "$MODULE_DIR/disable"
fi
if [ -f "$MODULE_DIR/remove" ]; then
log "ALERT: Module removal detected via Magisk UI - removing remove flag"
rm -f "$MODULE_DIR/remove"
fi
}
protect_hosts() {
if [ ! -f "$HOSTS_BACKUP" ] || [ ! -f "$MODULE_HOSTS" ]; then
return
fi
if ! cmp -s "$MODULE_HOSTS" "$HOSTS_BACKUP"; then
log "ALERT: Hosts tampering detected - restoring"
cp "$HOSTS_BACKUP" "$MODULE_HOSTS"
fi
}
log "=== Watchdog starting ==="
tick_count=0
while true; do
protect_module_flags
if is_enabled; then
if [ $((tick_count % HOSTS_CHECK_EVERY_TICKS)) -eq 0 ]; then
protect_hosts
fi
fi
tick_count=$((tick_count + 1))
sleep "$LOOP_SLEEP_SECONDS"
done
WATCHDOG
}
start_watchdog() {
nohup sh "$WATCHDOG_SCRIPT" >/dev/null 2>&1 &
}
post_fs_main() {
mkdir -p "$GUARDIAN_DIR"
log "Guardian module loading"
write_watchdog_script
chmod 755 "$WATCHDOG_SCRIPT"
if [ "${ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START:-0}" -ne 1 ]; then
start_watchdog
log "Watchdog started"
return
fi
log "Watchdog generation complete (start skipped)"
}
if [ "${ANDROID_GUARDIAN_POST_FS_SKIP_MAIN:-0}" -ne 1 ]; then
post_fs_main
fi

View File

@ -1,143 +0,0 @@
#!/system/bin/sh
# Android Guardian Service - runs at boot
# This service:
# 1. Monitors and protects the hosts file
# 2. Blocks installation of forbidden apps
# 3. Prevents module from being disabled via Magisk UI
# 4. Can only be stopped via ADB with the correct command
MODDIR=${0%/*}
GUARDIAN_DIR="${ANDROID_GUARDIAN_DIR:-/data/adb/android_guardian}"
LOG_FILE="$GUARDIAN_DIR/guardian.log"
BLOCKED_APPS_FILE="$GUARDIAN_DIR/blocked_apps.txt"
CONTROL_FILE="$GUARDIAN_DIR/control"
HOSTS_BACKUP="$GUARDIAN_DIR/hosts.backup"
MODULE_DIR="${ANDROID_GUARDIAN_MODULE_DIR:-/data/adb/modules/android_guardian}"
SYSTEM_HOSTS_FILE="${ANDROID_GUARDIAN_SYSTEM_HOSTS_FILE:-/system/etc/hosts}"
MODULE_HOSTS_FILE="${ANDROID_GUARDIAN_MODULE_HOSTS_FILE:-$MODDIR/system/etc/hosts}"
DISABLE_FILE="$MODULE_DIR/disable"
REMOVE_FILE="$MODULE_DIR/remove"
LOOP_SLEEP_SECONDS=5
HOSTS_CHECK_EVERY_TICKS=6
APPS_CHECK_EVERY_TICKS=12
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >>"$LOG_FILE"
}
initialize_service() {
mkdir -p "$GUARDIAN_DIR"
if [ ! -f "$CONTROL_FILE" ]; then
echo "ENABLED" >"$CONTROL_FILE"
fi
log "=== Android Guardian starting ==="
# Enable wireless ADB on boot (persistent port 5555)
setprop service.adb.tcp.port 5555
stop adbd
start adbd
log "Wireless ADB enabled on port 5555"
}
# Function to check if guardian is enabled (via ADB control, not Magisk UI)
is_enabled() {
[ "$(cat "$CONTROL_FILE" 2>/dev/null)" = "ENABLED" ]
}
# Function to protect module from being disabled via Magisk UI
protect_module() {
# Remove disable file if someone tried to disable via Magisk
if [ -f "$DISABLE_FILE" ]; then
log "Module disable attempt detected via Magisk UI! Re-enabling..."
rm -f "$DISABLE_FILE"
log "Module re-enabled"
fi
# Remove remove file if someone tried to uninstall via Magisk
if [ -f "$REMOVE_FILE" ]; then
log "Module removal attempt detected via Magisk UI! Blocking..."
rm -f "$REMOVE_FILE"
log "Module removal blocked"
fi
}
# Function to restore hosts file if tampered
protect_hosts() {
if [ -f "$HOSTS_BACKUP" ]; then
if ! cmp -s "$SYSTEM_HOSTS_FILE" "$HOSTS_BACKUP"; then
log "Hosts file tampering detected! Restoring..."
cp "$HOSTS_BACKUP" "$MODULE_HOSTS_FILE"
log "Hosts file restored"
fi
fi
}
# Function to uninstall blocked apps
check_blocked_apps() {
if [ ! -f "$BLOCKED_APPS_FILE" ]; then
return
fi
installed_packages=$(pm list packages 2>/dev/null) || installed_packages=""
if [ -z "$installed_packages" ]; then
return
fi
installed_packages="
$installed_packages
"
while IFS= read -r package || [ -n "$package" ]; do
# Skip comments and empty lines
case "$package" in
\#* | "") continue ;;
esac
# Check if package is installed
case "$installed_packages" in
*"
package:$package
"*)
log "Blocked app detected: $package - Uninstalling..."
if pm uninstall "$package" 2>/dev/null; then
log "Uninstalled: $package"
else
log "Failed to uninstall: $package"
fi
;;
esac
done <"$BLOCKED_APPS_FILE"
}
guardian_loop() {
tick_count=0
while true; do
# ALWAYS protect module from UI disabling (even if guardian is "disabled" via ADB)
# This ensures only ADB can control the guardian
protect_module
if is_enabled; then
if [ $((tick_count % HOSTS_CHECK_EVERY_TICKS)) -eq 0 ]; then
protect_hosts
fi
if [ $((tick_count % APPS_CHECK_EVERY_TICKS)) -eq 0 ]; then
check_blocked_apps
fi
fi
tick_count=$((tick_count + 1))
sleep "$LOOP_SLEEP_SECONDS"
done
}
service_main() {
initialize_service
guardian_loop &
log "Guardian service started (PID: $!)"
}
if [ "${ANDROID_GUARDIAN_SKIP_MAIN:-0}" -ne 1 ]; then
service_main
fi

View File

@ -1,16 +0,0 @@
#!/system/bin/sh
# Cleanup when module is uninstalled
GUARDIAN_DIR="/data/adb/android_guardian"
# Only allow uninstall if control file says DISABLED
if [ -f "$GUARDIAN_DIR/control" ]; then
status=$(cat "$GUARDIAN_DIR/control")
if [ "$status" != "DISABLED" ]; then
echo "Guardian is still enabled! Use ADB to disable first:"
echo " adb shell 'echo DISABLED > /data/adb/android_guardian/control'"
exit 1
fi
fi
# Clean up guardian data
rm -rf "$GUARDIAN_DIR"

View File

@ -9,6 +9,8 @@ build_module() {
local tmp_dir="$WORK_DIR/guardian_module"
local module_zip="$WORK_DIR/android_guardian.zip"
require_guardian_module
echo "[BUILD] Building Android Guardian module..." >&2
rm -rf "$tmp_dir"

View File

@ -10,10 +10,25 @@ source "$SCRIPT_DIR/../../lib/common.sh"
# shellcheck source=../../lib/android.sh
source "$SCRIPT_DIR/../../lib/android.sh"
GUARDIAN_MODULE_DIR="$SCRIPT_DIR/android_guardian"
# The module moved to github.com/kuhyx/android-guardian. This pointed at a
# sibling directory that never existed here even before the extraction, so the
# script could not have packaged a module from this path; prefer a checkout and
# say so loudly rather than failing later inside zip.
GUARDIAN_MODULE_DIR="${ANDROID_GUARDIAN_DIR:-$HOME/android-guardian}/module"
GUARDIAN_DATA_DIR="/data/adb/android_guardian"
MODULE_DEST="/data/adb/modules/android_guardian"
# Fail here, not four helpers deep inside a cp. Every packaging step below
# reads GUARDIAN_MODULE_DIR unguarded, so an absent checkout would otherwise
# surface as a bare "cp: cannot stat".
require_guardian_module() {
[[ -f "$GUARDIAN_MODULE_DIR/module.prop" ]] && return 0
die "android-guardian module not found at ${GUARDIAN_MODULE_DIR}.
It lives in its own repo now:
git clone https://github.com/kuhyx/android-guardian ~/android-guardian
Or point this script elsewhere with ANDROID_GUARDIAN_DIR."
}
# Ensure android-tools (adb) is installed
ensure_adb_installed() {
if command -v adb &>/dev/null; then
@ -90,7 +105,6 @@ source "$SCRIPT_DIR/lib/android_module.sh"
# shellcheck source=lib/android_commands.sh
source "$SCRIPT_DIR/lib/android_commands.sh"
# Main
# Initialize Android script (handles sudo, sets WORK_DIR)
init_android_script "$@"

View File

@ -1,72 +0,0 @@
#!/bin/bash
# Regression tests for android_guardian post-fs-data watchdog generation.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
TARGET_SCRIPT="$REPO_DIR/scripts/periodic_background/utils/android_guardian/post-fs-data.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_file_contains() {
local file_path="$1"
local pattern="$2"
local context="$3"
grep -q -- "$pattern" "$file_path" || fail "$context"
}
assert_file_not_contains() {
local file_path="$1"
local pattern="$2"
local context="$3"
if grep -q -- "$pattern" "$file_path"; then
fail "$context"
fi
}
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
GUARDIAN_DIR="$TMP_DIR/guardian"
MODULE_DIR="$TMP_DIR/module"
WATCHDOG_SCRIPT="$GUARDIAN_DIR/watchdog.sh"
mkdir -p "$GUARDIAN_DIR" "$MODULE_DIR/system/etc"
printf 'Generating watchdog script in a temp Android guardian directory...\n'
ANDROID_GUARDIAN_DIR="$GUARDIAN_DIR" \
ANDROID_GUARDIAN_MODULE_DIR="$MODULE_DIR" \
ANDROID_GUARDIAN_WATCHDOG_SCRIPT="$WATCHDOG_SCRIPT" \
ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START=1 \
sh "$TARGET_SCRIPT"
[[ -f "$WATCHDOG_SCRIPT" ]] || fail 'watchdog script should be generated'
[[ -x "$WATCHDOG_SCRIPT" ]] || fail 'watchdog script should be executable'
printf 'Checking generated watchdog cadence and lower-fork host protection...\n'
assert_file_contains "$WATCHDOG_SCRIPT" '^HOSTS_CHECK_EVERY_TICKS=10$' \
'watchdog should throttle host protection to every 10 ticks'
assert_file_contains "$WATCHDOG_SCRIPT" '^LOOP_SLEEP_SECONDS=3$' \
'watchdog should keep the 3 second base sleep'
assert_file_contains "$WATCHDOG_SCRIPT" 'cmp -s' \
'watchdog should use cmp for host integrity checks'
assert_file_not_contains "$WATCHDOG_SCRIPT" 'md5sum' \
'watchdog should not use md5sum in the hot loop anymore'
assert_file_not_contains "$WATCHDOG_SCRIPT" 'cut -d' \
'watchdog should not pipe hashes through cut anymore'
printf 'Checking test hooks and generation log entry...\n'
assert_file_contains "$TARGET_SCRIPT" 'ANDROID_GUARDIAN_POST_FS_SKIP_MAIN' \
'post-fs-data should support skipping main for tests'
assert_file_contains "$TARGET_SCRIPT" 'ANDROID_GUARDIAN_POST_FS_SKIP_WATCHDOG_START' \
'post-fs-data should support generating without starting the watchdog'
assert_file_contains "$GUARDIAN_DIR/guardian.log" 'Watchdog generation complete (start skipped)' \
'post-fs-data should log skipped watchdog starts during tests'
printf 'android_guardian post-fs-data regression checks passed.\n'

View File

@ -1,72 +0,0 @@
#!/bin/bash
# Regression tests for android_guardian service loop cadence.
set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
TARGET_SCRIPT="$REPO_DIR/scripts/periodic_background/utils/android_guardian/service.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_equals() {
local expected="$1"
local actual="$2"
local context="$3"
if [[ "$expected" != "$actual" ]]; then
fail "$context (expected: '$expected', actual: '$actual')"
fi
}
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
WORKTREE="$TMP_DIR/worktree"
mkdir -p "$WORKTREE/scripts/periodic_background/utils/android_guardian"
cp "$TARGET_SCRIPT" "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh"
printf 'Checking skip-main avoids boot side effects...\n'
SKIP_MAIN_GUARDIAN_DIR="$TMP_DIR/skip-main-guardian"
ANDROID_GUARDIAN_SKIP_MAIN=1 \
ANDROID_GUARDIAN_DIR="$SKIP_MAIN_GUARDIAN_DIR" \
ANDROID_GUARDIAN_MODULE_DIR="$TMP_DIR/skip-main-module" \
sh "$TARGET_SCRIPT"
[[ ! -d "$SKIP_MAIN_GUARDIAN_DIR" ]] ||
fail 'skip-main should prevent guardian boot setup side effects'
printf 'Checking guardian loop cadence constants...\n'
hosts_ticks=$(grep '^HOSTS_CHECK_EVERY_TICKS=' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" | cut -d= -f2)
apps_ticks=$(grep '^APPS_CHECK_EVERY_TICKS=' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" | cut -d= -f2)
sleep_seconds=$(grep '^LOOP_SLEEP_SECONDS=' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" | cut -d= -f2)
assert_equals '6' "$hosts_ticks" 'hosts protection should run every 6 ticks'
assert_equals '12' "$apps_ticks" 'blocked-app scan should run every 12 ticks'
assert_equals '5' "$sleep_seconds" 'guardian loop should keep the 5 second base sleep'
printf 'Checking guardian loop protects module every tick...\n'
grep -q 'protect_module' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" ||
fail 'guardian loop must always protect the module each tick'
printf 'Checking blocked-app scans are cached per pass...\n'
# Literal search pattern: the $ must reach grep as text, not be expanded
# by this shell.
# shellcheck disable=SC2016
grep -q 'installed_packages=$(pm list packages 2>/dev/null)' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" ||
fail 'guardian service should cache installed packages once per blocked-app scan'
if grep -q 'pm list packages 2>/dev/null | grep -q' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh"; then
fail 'guardian service should not re-run pm list packages through grep for every blocked app'
fi
printf 'Checking guardian loop uses skip-main guard for tests...\n'
grep -q 'ANDROID_GUARDIAN_SKIP_MAIN' "$WORKTREE/scripts/periodic_background/utils/android_guardian/service.sh" ||
fail 'guardian service should support skip-main testing'
printf 'android_guardian service regression checks passed.\n'