phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
#!/system/bin/sh
|
|
|
|
|
# shellcheck shell=ash
|
|
|
|
|
# ============================================================
|
|
|
|
|
# Hosts file enforcer for rooted Android.
|
|
|
|
|
#
|
|
|
|
|
# Mirrors the PC-side guard in linux_configuration/hosts/ but
|
|
|
|
|
# for /system/etc/hosts on Android, which has no chattr, no
|
|
|
|
|
# systemd, and where /system is read-only.
|
|
|
|
|
#
|
|
|
|
|
# Strategy (defense in depth):
|
|
|
|
|
# 1. Canonical hosts file lives at HOSTS_CANONICAL and is
|
|
|
|
|
# chattr +i (best-effort; ignored if kernel/fs rejects).
|
|
|
|
|
# 2. Bind-mount HOSTS_CANONICAL read-only over HOSTS_TARGET so
|
|
|
|
|
# that even `echo > /system/etc/hosts` fails for everyone,
|
|
|
|
|
# including root-in-a-terminal-app, without re-mounting.
|
|
|
|
|
# 3. A watchdog loop re-asserts the bind mount and verifies
|
|
|
|
|
# sha256 every HOSTS_CHECK_INTERVAL seconds.
|
|
|
|
|
#
|
|
|
|
|
# Known limitation: a user with root *and* willingness to run
|
|
|
|
|
# `umount /system/etc/hosts; mount -o remount,rw /system ...`
|
|
|
|
|
# can still bypass this. Making it "impossible without USB" is
|
|
|
|
|
# not achievable on a rooted phone with a local terminal.
|
|
|
|
|
# This enforcer closes the one-liner gap and adds logging so
|
|
|
|
|
# tampering leaves an audit trail.
|
|
|
|
|
# ============================================================
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
. "$SCRIPT_DIR/config.sh"
|
|
|
|
|
|
|
|
|
|
PIDFILE="$STATE_DIR/hosts_enforcer.pid"
|
2026-05-01 19:07:27 +02:00
|
|
|
MISSING_TARGET_LOGGED=0
|
|
|
|
|
MAGISK_HOSTS_LOGGED=0
|
|
|
|
|
# Magisk "Systemless Hosts" module path. When this module is enabled,
|
|
|
|
|
# Magisk magic-mounts files placed under its system/ tree onto the live
|
|
|
|
|
# /system at boot. Copying our canonical hosts there makes Magisk overlay
|
|
|
|
|
# /system/etc/hosts on next boot, even on read-only system partitions.
|
|
|
|
|
MAGISK_HOSTS_MODULE_DIR="/data/adb/modules/hosts"
|
|
|
|
|
MAGISK_HOSTS_TARGET="$MAGISK_HOSTS_MODULE_DIR/system/etc/hosts"
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
|
|
|
|
|
mkdir -p "$STATE_DIR" "$(dirname "$HOSTS_CANONICAL")"
|
|
|
|
|
touch "$HOSTS_LOG"
|
|
|
|
|
chmod 666 "$HOSTS_LOG" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
log() {
|
|
|
|
|
local ts
|
|
|
|
|
ts="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
|
echo "[$ts] $1" >> "$HOSTS_LOG"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rotate_log() {
|
|
|
|
|
local lines
|
|
|
|
|
lines="$(wc -l < "$HOSTS_LOG" 2>/dev/null || echo 0)"
|
|
|
|
|
if [ "$lines" -gt 500 ]; then
|
|
|
|
|
local tmp="$HOSTS_LOG.tmp"
|
|
|
|
|
tail -n 500 "$HOSTS_LOG" > "$tmp"
|
|
|
|
|
mv "$tmp" "$HOSTS_LOG"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
acquire_lock() {
|
|
|
|
|
if [ -f "$PIDFILE" ]; then
|
|
|
|
|
local old_pid
|
|
|
|
|
old_pid="$(cat "$PIDFILE")"
|
|
|
|
|
if kill -0 "$old_pid" 2>/dev/null; then
|
|
|
|
|
local cmdline
|
|
|
|
|
cmdline="$(cat "/proc/$old_pid/cmdline" 2>/dev/null | tr '\0' ' ')"
|
|
|
|
|
if echo "$cmdline" | grep -q "hosts_enforcer"; then
|
|
|
|
|
echo "hosts_enforcer already running (PID $old_pid)"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
rm -f "$PIDFILE"
|
|
|
|
|
fi
|
|
|
|
|
echo $$ > "$PIDFILE"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sha256_of() {
|
|
|
|
|
# Android's toybox has sha256sum; fall back to md5sum if missing.
|
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
|
|
|
sha256sum "$1" 2>/dev/null | awk '{print $1}'
|
|
|
|
|
else
|
|
|
|
|
md5sum "$1" 2>/dev/null | awk '{print $1}'
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is_bind_mounted_correctly() {
|
|
|
|
|
# Android devices often already have /system/etc/hosts as its own mount
|
|
|
|
|
# point (OEM overlay / f2fs block). A mere "path is in /proc/self/mounts"
|
|
|
|
|
# check is not enough - we must verify the mounted content matches our
|
|
|
|
|
# canonical by hash. Otherwise we'd accept OEM mounts as our own.
|
|
|
|
|
if [ ! -f "$HOSTS_TARGET" ]; then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
local target_hash canonical_hash
|
|
|
|
|
target_hash="$(sha256_of "$HOSTS_TARGET")"
|
|
|
|
|
canonical_hash="$(sha256_of "$HOSTS_CANONICAL")"
|
|
|
|
|
[ -n "$target_hash" ] && [ "$target_hash" = "$canonical_hash" ]
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 19:07:27 +02:00
|
|
|
has_hosts_target() {
|
|
|
|
|
[ -f "$HOSTS_TARGET" ]
|
|
|
|
|
}
|
|
|
|
|
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
unmount_existing_hosts_mount() {
|
|
|
|
|
# If anything else is already mounted on /system/etc/hosts (OEM overlay
|
|
|
|
|
# or a previous failed bind), unmount it so we can take its place.
|
|
|
|
|
local attempts=0
|
|
|
|
|
while grep -qE "[[:space:]]${HOSTS_TARGET}[[:space:]]" /proc/self/mounts 2>/dev/null; do
|
|
|
|
|
if [ "$attempts" -ge 5 ]; then
|
|
|
|
|
log "Could not fully unmount $HOSTS_TARGET after 5 attempts"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
umount "$HOSTS_TARGET" 2>/dev/null \
|
|
|
|
|
|| umount -l "$HOSTS_TARGET" 2>/dev/null \
|
|
|
|
|
|| break
|
|
|
|
|
attempts=$((attempts + 1))
|
|
|
|
|
done
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
make_target_writable_once() {
|
|
|
|
|
# /system is usually mounted read-only. Make it rw just long enough
|
|
|
|
|
# to overwrite HOSTS_TARGET with the canonical content, then remount ro.
|
|
|
|
|
local system_mount
|
|
|
|
|
system_mount="$(awk '$2=="/system"{print $2; exit}' /proc/self/mounts)"
|
|
|
|
|
if [ -z "$system_mount" ]; then
|
|
|
|
|
system_mount="/system"
|
|
|
|
|
fi
|
|
|
|
|
mount -o remount,rw "$system_mount" 2>/dev/null || true
|
|
|
|
|
chattr -i "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
cp "$HOSTS_CANONICAL" "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
chmod 644 "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
chattr +i "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
mount -o remount,ro "$system_mount" 2>/dev/null || true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert_bind_mount() {
|
2026-05-01 19:07:27 +02:00
|
|
|
if ! has_hosts_target; then
|
|
|
|
|
# Target file doesn't exist yet - try to create it by directly writing
|
|
|
|
|
# /system (remount rw briefly). On Magisk-rooted devices this usually
|
|
|
|
|
# works because Magisk intercepts the remount. If it fails we fall back
|
|
|
|
|
# to the firewall-only path and log a warning.
|
|
|
|
|
log "hosts target missing - attempting to create $HOSTS_TARGET via /system remount"
|
|
|
|
|
make_target_writable_once
|
|
|
|
|
if ! has_hosts_target; then
|
|
|
|
|
if [ "$MISSING_TARGET_LOGGED" -eq 0 ]; then
|
|
|
|
|
log "WARN: could not create $HOSTS_TARGET on this ROM (hosts bind enforcement disabled)"
|
|
|
|
|
MISSING_TARGET_LOGGED=1
|
|
|
|
|
fi
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
log "Created and populated $HOSTS_TARGET directly"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
if is_bind_mounted_correctly; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
# Something is in the way (OEM overlay or previous partial mount).
|
|
|
|
|
unmount_existing_hosts_mount
|
|
|
|
|
# Try plain bind mount - no remount-rw of /system needed.
|
2026-05-01 19:07:27 +02:00
|
|
|
# Android toybox mount commonly supports "-o bind" but not "--bind".
|
|
|
|
|
if mount -o bind "$HOSTS_CANONICAL" "$HOSTS_TARGET" 2>/dev/null; then
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
mount -o remount,ro,bind "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
if is_bind_mounted_correctly; then
|
|
|
|
|
log "Bind-mounted $HOSTS_CANONICAL over $HOSTS_TARGET"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
log "Bind mount reported success but target still mismatches - unmounting"
|
|
|
|
|
umount "$HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
# Bind failed - fall back to direct overwrite of /system/etc/hosts.
|
|
|
|
|
log "Bind mount failed - falling back to direct overwrite"
|
|
|
|
|
make_target_writable_once
|
|
|
|
|
if is_bind_mounted_correctly; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure_canonical_immutable() {
|
|
|
|
|
chmod 644 "$HOSTS_CANONICAL" 2>/dev/null || true
|
|
|
|
|
chattr +i "$HOSTS_CANONICAL" 2>/dev/null || true
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 19:07:27 +02:00
|
|
|
# Populate the Magisk "Systemless Hosts" module. Magisk's magic mount picks
|
|
|
|
|
# up files under /data/adb/modules/<id>/system/ at boot and overlays them
|
|
|
|
|
# onto the live /system tree. By placing our canonical hosts there we get
|
|
|
|
|
# /system/etc/hosts on next boot even on ROMs whose system partition is
|
|
|
|
|
# truly read-only (where remount,rw silently fails).
|
|
|
|
|
# Returns 0 if module is present and now in sync, 1 otherwise.
|
|
|
|
|
populate_magisk_hosts_module() {
|
|
|
|
|
if [ ! -d "$MAGISK_HOSTS_MODULE_DIR" ]; then
|
|
|
|
|
if [ "$MAGISK_HOSTS_LOGGED" -eq 0 ]; then
|
|
|
|
|
log "WARN: Magisk hosts module dir absent ($MAGISK_HOSTS_MODULE_DIR); enable 'Systemless Hosts' in the Magisk app."
|
|
|
|
|
MAGISK_HOSTS_LOGGED=1
|
|
|
|
|
fi
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
if [ -f "$MAGISK_HOSTS_MODULE_DIR/disable" ] || [ -f "$MAGISK_HOSTS_MODULE_DIR/remove" ]; then
|
|
|
|
|
if [ "$MAGISK_HOSTS_LOGGED" -eq 0 ]; then
|
|
|
|
|
log "WARN: Magisk hosts module is disabled or pending removal"
|
|
|
|
|
MAGISK_HOSTS_LOGGED=1
|
|
|
|
|
fi
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
mkdir -p "$(dirname "$MAGISK_HOSTS_TARGET")" 2>/dev/null || true
|
|
|
|
|
local module_hash canonical_hash
|
|
|
|
|
module_hash="$(sha256_of "$MAGISK_HOSTS_TARGET")"
|
|
|
|
|
canonical_hash="$(sha256_of "$HOSTS_CANONICAL")"
|
|
|
|
|
if [ -n "$module_hash" ] && [ "$module_hash" = "$canonical_hash" ]; then
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if cp "$HOSTS_CANONICAL" "$MAGISK_HOSTS_TARGET" 2>/dev/null; then
|
|
|
|
|
chmod 644 "$MAGISK_HOSTS_TARGET" 2>/dev/null || true
|
|
|
|
|
log "Synced canonical hosts -> Magisk module ($MAGISK_HOSTS_TARGET); active after next reboot"
|
|
|
|
|
MAGISK_HOSTS_LOGGED=0
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
log "ERROR: failed to copy canonical hosts to $MAGISK_HOSTS_TARGET"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
verify_and_restore() {
|
|
|
|
|
if [ ! -f "$HOSTS_CANONICAL" ]; then
|
|
|
|
|
log "ERROR: canonical hosts missing at $HOSTS_CANONICAL"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local expected
|
|
|
|
|
expected="$(cat "$HOSTS_SHA_FILE" 2>/dev/null)"
|
|
|
|
|
if [ -z "$expected" ]; then
|
|
|
|
|
expected="$(sha256_of "$HOSTS_CANONICAL")"
|
|
|
|
|
echo "$expected" > "$HOSTS_SHA_FILE"
|
|
|
|
|
chmod 644 "$HOSTS_SHA_FILE" 2>/dev/null || true
|
|
|
|
|
chattr +i "$HOSTS_SHA_FILE" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Canonical integrity check
|
|
|
|
|
local actual_canonical
|
|
|
|
|
actual_canonical="$(sha256_of "$HOSTS_CANONICAL")"
|
|
|
|
|
if [ "$actual_canonical" != "$expected" ]; then
|
|
|
|
|
log "TAMPER: canonical hash mismatch (expected $expected, got $actual_canonical)"
|
|
|
|
|
# We cannot fix the canonical from here - it is the source of truth.
|
|
|
|
|
# Just log and continue; deploy.sh must re-push.
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-05-01 19:07:27 +02:00
|
|
|
if ! has_hosts_target; then
|
|
|
|
|
if [ "$MISSING_TARGET_LOGGED" -eq 0 ]; then
|
|
|
|
|
log "WARN: hosts target missing on this ROM: $HOSTS_TARGET (integrity checks skipped)"
|
|
|
|
|
MISSING_TARGET_LOGGED=1
|
|
|
|
|
fi
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
MISSING_TARGET_LOGGED=0
|
|
|
|
|
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
# Live target integrity check
|
|
|
|
|
local actual_target
|
|
|
|
|
actual_target="$(sha256_of "$HOSTS_TARGET")"
|
|
|
|
|
if [ "$actual_target" != "$expected" ]; then
|
|
|
|
|
log "TAMPER: $HOSTS_TARGET hash mismatch - restoring"
|
|
|
|
|
assert_bind_mount
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
|
log "hosts_enforcer shutting down"
|
|
|
|
|
rm -f "$PIDFILE"
|
|
|
|
|
exit 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trap cleanup INT TERM
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
acquire_lock
|
|
|
|
|
log "hosts_enforcer started (PID=$$)"
|
|
|
|
|
|
|
|
|
|
ensure_canonical_immutable
|
2026-05-01 19:07:27 +02:00
|
|
|
# Seed the Magisk systemless hosts module so /system/etc/hosts gets
|
|
|
|
|
# magic-mounted on next boot.
|
|
|
|
|
populate_magisk_hosts_module || true
|
|
|
|
|
# Initial assertion (covers the case where target already exists).
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
assert_bind_mount || true
|
|
|
|
|
|
|
|
|
|
# Seed sha file if missing
|
|
|
|
|
if [ ! -f "$HOSTS_SHA_FILE" ]; then
|
|
|
|
|
sha256_of "$HOSTS_CANONICAL" > "$HOSTS_SHA_FILE"
|
|
|
|
|
chmod 644 "$HOSTS_SHA_FILE" 2>/dev/null || true
|
|
|
|
|
chattr +i "$HOSTS_SHA_FILE" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
while true; do
|
2026-05-01 19:07:27 +02:00
|
|
|
populate_magisk_hosts_module || true
|
phone_focus_mode: add persistent home-mode status notification
- New companion Android app (com.kuhy.focusstatus) under
phone_focus_mode/focus_status_app/ with a pure-Java, Gradle-less
command-line build pipeline (build.sh). Shows an ongoing
notification titled 'Focus: HOME / AWAY / DAEMON DOWN' with
distance, GPS, disabled-app count, last check, daemon checkmarks,
and a 'Re-check now' action button.
- focus_daemon.sh: write_status_snapshot() + sleep_with_recheck()
for JSON status + early-wake on trigger file. init() chmods
STATE_DIR 777 so the app can drop the trigger file.
- config.sh: new STATUS_FILE / RECHECK_TRIGGER; WHITELIST expanded
with com.kuhy.focusstatus and 11 more user-requested apps
(podcini X, mpv, bible/openbible, pkp/portalpasazera, orange,
runnerup, splitbills/splitwise, xiaomi smarthome).
- focus_ctl.sh: new 'recheck' + 'notif-status' subcommands.
- deploy.sh: new step [7/7] builds APK, installs, grants
POST_NOTIFICATIONS, pre-approves Magisk SU policy, launches
foreground service.
- .gitignore: exclude focus_status_app/build symlink + debug.keystore.
End-to-end verified on device: notification live with real values;
Re-check button triggers a daemon location check within ~1s.
2026-04-20 15:33:32 +02:00
|
|
|
verify_and_restore
|
|
|
|
|
rotate_log
|
|
|
|
|
sleep "$HOSTS_CHECK_INTERVAL"
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|