diff --git a/android/0_termux-setup_v2.sh b/android/0_termux-setup_v2.sh index b01e6e2..a95fbe3 100644 --- a/android/0_termux-setup_v2.sh +++ b/android/0_termux-setup_v2.sh @@ -1,141 +1,215 @@ #!/data/data/com.termux/files/usr/bin/bash set -euo pipefail -# IIAB on Android - Termux bootstrap (standalone) -# -# Requirements: -# - Termux installed -# - Network connectivity -# - Shizuku + rish (Android 12/13 only) to raise Phantom Process Killer (PPK) limit -# -# Default behavior (idempotent): -# - One-time Termux repo mirror selection (prompted once; no TTY checks) -# - Acquire wakelock when possible (Termux:API) -# - Prepare Termux baseline packages (noninteractive, avoids dpkg conffile prompts) -# - Ensure proot-distro + Debian exists (install by default) -# - Last step: On Android 12/13 only, re-apply PPK fix via rish (if available) -# - Self-check summary at the end -# -# Flags: -# - --ppk-only Run only PPK fix + self-check -# - --reset-debian Reset (reinstall) Debian (clean environment), then proceed normally -# - --cleanup-rish-export After installing rish into PATH, delete ~/rish and ~/rish*.dex (off by default) - -RED="\033[31m" -YEL="\033[33m" -GRN="\033[32m" -BLU="\033[34m" -RST="\033[0m" -BOLD="\033[1m" +# 0_termux-setupv2.sh +# - Termux bootstrap (packages, wakelock) +# - proot-distro + Debian bootstrap +# - ADB wireless pair/connect via Termux:API notifications (no Shizuku) +# - Optional PPK / phantom-process tweaks (best-effort) +RED="\033[31m"; YEL="\033[33m"; GRN="\033[32m"; BLU="\033[34m"; RST="\033[0m"; BOLD="\033[1m" log() { printf "${BLU}[iiab]${RST} %s\n" "$*"; } ok() { printf "${GRN}[iiab]${RST} %s\n" "$*"; } warn() { printf "${YEL}[iiab] WARNING:${RST} %s\n" "$*" >&2; } warn_red() { printf "${RED}${BOLD}[iiab] WARNING:${RST} %s\n" "$*" >&2; } have() { command -v "$1" >/dev/null 2>&1; } +need() { have "$1" || return 1; } +die() { echo "[!] $*" >&2; exit 1; } +# ------------------------- +# Logging +# ------------------------- +LOG_ENABLED=1 +LOG_FILE="" # if empty, auto-generate under $LOG_DIR +LOG_KEEP=20 # keep last N logs + +rotate_logs() { + [[ -d "${LOG_DIR:-}" ]] || return 0 + local n=$((LOG_KEEP + 1)) + while IFS= read -r f; do + [[ -n "${f:-}" ]] || continue + [[ -n "${LOG_FILE:-}" && "$f" == "$LOG_FILE" ]] && continue + rm -f -- "$f" 2>/dev/null || true + done < <(ls -1t "$LOG_DIR"/*.log 2>/dev/null | tail -n +"$n" || true) +} + +setup_logging() { + # If logging is disabled, still allow --debug to trace to console. + if [[ "${LOG_ENABLED:-1}" -ne 1 ]]; then + if [[ "${DEBUG:-0}" -eq 1 ]]; then + set -x + ok "Debug trace enabled (bash -x) -> console (logging disabled)" + fi + return 0 + fi + + mkdir -p "$LOG_DIR" 2>/dev/null || true + + if [[ -z "${LOG_FILE:-}" ]]; then + LOG_FILE="${LOG_DIR}/0_termux-setupv2.$(date +%Y%m%d-%H%M%S).log" + else + # Best-effort: ensure parent dir exists + mkdir -p "$(dirname -- "$LOG_FILE")" 2>/dev/null || true + fi + + # Header (best-effort) + { + echo "=== iiab termux setup v2 log ===" + echo "Started: $(date -Is)" + echo "Script: $0" + echo "Args: ${*:-}" + echo "Android SDK=${ANDROID_SDK:-?} Release=${ANDROID_REL:-?}" + echo "PWD: $(pwd 2>/dev/null || true)" + echo "================================" + } >>"$LOG_FILE" 2>/dev/null || true + + # Best-effort: restrict log readability (may include debug/xtrace) + chmod 600 "$LOG_FILE" 2>/dev/null || true + + rotate_logs + + # Duplicate stdout/stderr to console + log (strip ANSI in log) + exec \ + > >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE")) \ + 2> >(tee >(sed -E 's/\x1B\[[0-9;]*[ -/]*[@-~]//g' >>"$LOG_FILE") >&2) + + ok "Logging to: $LOG_FILE" + + # If --debug, send xtrace only to log + if [[ "${DEBUG:-0}" -eq 1 ]]; then + exec 9>>"$LOG_FILE" + export BASH_XTRACEFD=9 + export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + set -x + ok "Debug trace enabled (bash -x) -> log only" + fi +} + +# ------------------------- +# Defaults +# ------------------------- STATE_DIR="${HOME}/.iiab-android" -mkdir -p "$STATE_DIR" +ADB_STATE_DIR="${STATE_DIR}/adbw_pair" +LOG_DIR="${STATE_DIR}/logs" +mkdir -p "$STATE_DIR" "$ADB_STATE_DIR" + +HOST="127.0.0.1" +CONNECT_PORT="" +TIMEOUT_SECS=180 +NOTIF_BASE_ID=9400 +NOTIF_SEQ=0 +LAST_NOTIF_ID="" +CLEANUP_OFFLINE=1 +DEBUG=0 -PPK_ONLY=0 RESET_DEBIAN=0 -CLEANUP_RISH_EXPORT=0 +ONLY_CONNECT=0 -for arg in "$@"; do - case "$arg" in - --ppk-only) PPK_ONLY=1 ;; - --reset-debian|--clean-debian) RESET_DEBIAN=1 ;; - --cleanup-rish-export) CLEANUP_RISH_EXPORT=1 ;; - -h|--help) - cat <<'EOF' +CHECK_NO_ADB=0 +CHECK_SDK="" +CHECK_MON="" +CHECK_PPK="" + +# Modes are mutually exclusive (baseline is default) +MODE="baseline" # baseline|with-adb|adb-only|connect-only|ppk-only|check|all +MODE_SET=0 + +# Termux apt options (avoid conffile prompts) +TERMUX_APT_OPTS=( "-y" "-o" "Dpkg::Options::=--force-confdef" "-o" "Dpkg::Options::=--force-confold" ) +termux_apt() { apt-get "${TERMUX_APT_OPTS[@]}" "$@"; } + +usage() { + cat <<'EOF' Usage: - ./0_termux-setup.sh - Default run (idempotent): Termux baseline + Debian bootstrap + PPK fix (Android 12/13) + self-check + ./0_termux-setupv2.sh + -> Termux baseline + Debian bootstrap (idempotent). No ADB prompts. - ./0_termux-setup.sh --ppk-only - Run ONLY the last step: PPK fix (Android 12/13) + self-check + ./0_termux-setupv2.sh --with-adb + -> Termux baseline + Debian bootstrap + ADB pair/connect if needed (skips if already connected). - ./0_termux-setup.sh --reset-debian - Reset (reinstall) Debian in proot-distro (clean environment), then proceed normally + ./0_termux-setupv2.sh --adb-only + -> Only ADB pair/connect if needed (no Debian; skips if already connected). - ./0_termux-setup.sh --cleanup-rish-export - After installing rish into PATH, delete ~/rish and ~/rish*.dex (off by default) + ./0_termux-setupv2.sh --connect-only [CONNECT_PORT] + -> Connect-only (no pairing). Use this after the device was already paired before. + + ./0_termux-setupv2.sh --ppk-only + -> Set PPK only: max_phantom_processes=256 (requires ADB already connected). + Android 14-16 usually achieve this via "Disable child process restrictions" in Developer Options. + + ./0_termux-setupv2.sh --check + -> Check readiness: developer options flag (if readable), + (Android 14+) "Disable child process restrictions" proxy flag, and (Android 12-13) PPK effective value. + + ./0_termux-setupv2.sh --all + -> baseline + Debian + ADB pair/connect if needed + (Android 12-13 only) apply --ppk + run --check. + + Optional: + --connect-port 41313 (5 digits) Skip CONNECT PORT prompt + --timeout 180 Seconds to wait per prompt + --reset-debian Reset (reinstall) Debian in proot-distro + --no-log Disable logging + --log-file /path/file Write logs to a specific file + --debug Extra logs Notes: - - This script will NOT delete rish exports unless --cleanup-rish-export is used. - - PPK fix is re-applied to 256 on every run (Android 12/13) when possible. - - Repo selection is prompted once (skipped for --ppk-only). +- ADB prompts require: `pkg install termux-api` + Termux:API app installed + notification permission. +- Wireless debugging must be enabled. +- This script never uses adb root. EOF - exit 0 - ;; - esac -done +} +# ------------------------- +# Android info +# ------------------------- get_android_sdk() { getprop ro.build.version.sdk 2>/dev/null || true; } get_android_release() { getprop ro.build.version.release 2>/dev/null || true; } - ANDROID_SDK="$(get_android_sdk)" ANDROID_REL="$(get_android_release)" -# Avoid dpkg conffile prompts (Termux layer) -TERMUX_APT_OPTS=( - "-y" - "-o" "Dpkg::Options::=--force-confdef" - "-o" "Dpkg::Options::=--force-confold" -) -termux_apt() { apt-get "${TERMUX_APT_OPTS[@]}" "$@"; } - # ------------------------- # Wakelock (Termux:API) # ------------------------- WAKELOCK_HELD=0 - acquire_wakelock() { - # This helps prevent the device from sleeping during long installs. if have termux-wake-lock; then - termux-wake-lock || true - WAKELOCK_HELD=1 - ok "Wakelock acquired (termux-wake-lock)." + if termux-wake-lock; then + WAKELOCK_HELD=1 + ok "Wakelock acquired (termux-wake-lock)." + else + warn "Failed to acquire wakelock (termux-wake-lock)." + fi else - warn "termux-wake-lock not available. (Install Termux:API: pkg install termux-api + Termux:API app.)" + warn "termux-wake-lock not available. Install: pkg install termux-api + Termux:API app." fi } - release_wakelock() { if [[ "$WAKELOCK_HELD" -eq 1 ]] && have termux-wake-unlock; then termux-wake-unlock || true ok "Wakelock released (termux-wake-unlock)." fi } - -trap 'release_wakelock' EXIT +trap 'cleanup_notif >/dev/null 2>&1 || true; release_wakelock >/dev/null 2>&1 || true' EXIT INT TERM # ------------------------- -# Step 0: One-time repo selector (no TTY checks) +# One-time repo selector # ------------------------- step_termux_repo_select_once() { local stamp="$STATE_DIR/stamp.termux_repo_selected" - if [[ -f "$stamp" ]]; then - return 0 - fi - + [[ -f "$stamp" ]] && return 0 if ! have termux-change-repo; then warn "termux-change-repo not found; skipping mirror selection." return 0 fi - # When running via "curl | bash", stdin is not a TTY. - # Try to prompt via /dev/tty if available. If not, skip WITHOUT stamping. if [[ -r /dev/tty ]]; then printf "\n${YEL}[iiab] One-time setup:${RST} Select a nearby Termux repository mirror for faster downloads.\n" >&2 - local ans="Y" if ! read -r -p "[iiab] Launch termux-change-repo now? [Y/n]: " ans < /dev/tty; then - warn "No interactive TTY available; skipping mirror selection (run the script directly to be prompted)." + warn "No interactive TTY available; skipping mirror selection (run script directly to be prompted)." return 0 fi - ans="${ans:-Y}" if [[ "$ans" =~ ^[Yy]$ ]]; then termux-change-repo || true @@ -143,17 +217,16 @@ step_termux_repo_select_once() { else warn "Mirror selection skipped by user." fi - date > "$stamp" return 0 fi - warn "No /dev/tty available; skipping mirror selection (run the script directly to be prompted)." + warn "No /dev/tty available; skipping mirror selection." return 0 } # ------------------------- -# Step 1: Termux baseline packages (idempotent) +# Baseline packages # ------------------------- step_termux_base() { local stamp="$STATE_DIR/stamp.termux_base" @@ -164,26 +237,32 @@ step_termux_base() { log "Updating Termux packages (noninteractive) and installing baseline dependencies..." export DEBIAN_FRONTEND=noninteractive - termux_apt update || true termux_apt upgrade || true termux_apt install \ - curl \ ca-certificates \ + curl \ coreutils \ grep \ sed \ + gawk \ openssh \ - proot \ - proot-distro || true + proot proot-distro \ + android-tools \ + termux-api \ + || true - ok "Termux baseline ready." - date > "$stamp" + if have proot-distro && have adb && have termux-notification; then + ok "Termux baseline ready." + date > "$stamp" + else + warn_red "Baseline incomplete (missing proot-distro/adb/termux-notification). Not stamping; rerun later." + fi } # ------------------------- -# Debian helpers (robust) +# Debian bootstrap # ------------------------- debian_exists() { have proot-distro || return 1 @@ -191,10 +270,8 @@ debian_exists() { } ensure_proot_distro() { - if have proot-distro; then - return 0 - fi - warn "proot-distro is not available. Attempting to install it..." + if have proot-distro; then return 0; fi + warn "proot-distro not found; attempting to install..." termux_apt install proot-distro || true have proot-distro } @@ -205,43 +282,35 @@ proot_install_debian_safe() { out="$(proot-distro install debian 2>&1)" rc=$? set -e - - if [[ $rc -eq 0 ]]; then - return 0 - fi + if [[ $rc -eq 0 ]]; then return 0; fi if echo "$out" | grep -qi "already installed"; then warn "Debian is already installed; continuing." return 0 fi - printf "%s\n" "$out" >&2 return $rc } -# ------------------------- -# Step 2: Ensure Debian exists (default), optionally reset it -# ------------------------- step_debian_bootstrap_default() { if ! ensure_proot_distro; then - warn "Unable to ensure proot-distro. Skipping Debian bootstrap." + warn "Unable to ensure proot-distro; skipping Debian bootstrap." return 0 fi if [[ "$RESET_DEBIAN" -eq 1 ]]; then warn "Reset requested: reinstalling Debian (clean environment)..." - # Prefer reset (if available), fallback to remove+install if proot-distro help 2>/dev/null | grep -qE '\breset\b'; then - proot-distro reset debian + proot-distro reset debian || true else - if debian_exists; then proot-distro remove debian; fi - proot_install_debian_safe + if debian_exists; then proot-distro remove debian || true; fi + proot_install_debian_safe || true fi else if debian_exists; then ok "Debian already present in proot-distro. Not reinstalling." else log "Installing Debian (proot-distro install debian)..." - proot_install_debian_safe + proot_install_debian_safe || true fi fi @@ -250,9 +319,7 @@ step_debian_bootstrap_default() { set -e export DEBIAN_FRONTEND=noninteractive apt-get update -y - apt-get -y \ - -o Dpkg::Options::=--force-confdef \ - -o Dpkg::Options::=--force-confold \ + apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \ install ca-certificates curl coreutils ' || true @@ -260,176 +327,617 @@ step_debian_bootstrap_default() { } # ------------------------- -# rish helpers +# Termux:API sanity check (notifications) # ------------------------- -android_major_12_to_13() { - case "${ANDROID_SDK:-}" in - 31|32) echo "12" ;; # Android 12 / 12L - 33) echo "13" ;; # Android 13 - *) echo "" ;; +termux_api_ready() { + have termux-notification || return 1 + + # Quick probe: some Samsung setups fail until Termux:API app is installed/allowed. + # We try a harmless notification and remove it. + local msg="iiab test notification" + if ! termux-notification --id "$NOTIF_BASE_ID" --title "iiab" --content "$msg" --priority max --sound >/dev/null 2>&1; then + return 1 + fi + if have termux-notification-remove; then + termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true + fi + return 0 +} + +sanitize_timeout() { + # Ensure TIMEOUT_SECS is a positive integer + if ! [[ "${TIMEOUT_SECS:-}" =~ ^[0-9]+$ ]]; then + warn "Invalid --timeout='${TIMEOUT_SECS:-}'. Falling back to 180." + TIMEOUT_SECS=180 + elif (( TIMEOUT_SECS < 5 )); then + warn "Very low --timeout=${TIMEOUT_SECS}. Forcing minimum 5 seconds." + TIMEOUT_SECS=5 + fi +} + +cleanup_notif() { + have termux-notification-remove || return 0 + termux-notification-remove "$NOTIF_BASE_ID" >/dev/null 2>&1 || true + if [[ -n "${LAST_NOTIF_ID:-}" ]]; then + termux-notification-remove "$LAST_NOTIF_ID" >/dev/null 2>&1 || true + fi +} + +notify_ask_one() { + # args: key title content + local key="$1" title="$2" content="$3" + local out="$ADB_STATE_DIR/$key.txt" + rm -f "$out" + + # Fresh notification each time + sound (use a new ID so Android plays sound each time) + local nid + nid=$((NOTIF_BASE_ID + 1 + NOTIF_SEQ)) + NOTIF_SEQ=$((NOTIF_SEQ + 1)) + LAST_NOTIF_ID="$nid" + + if have termux-notification-remove; then + termux-notification-remove "$nid" >/dev/null 2>&1 || true + fi + termux-notification \ + --id "$nid" \ + --ongoing \ + --priority max \ + --title "$title" \ + --content "$content" \ + --sound \ + --button1 "Answer" \ + --button1-action "sh -lc 'printf \"%s\" \"\$REPLY\" > \"$out\"'" + || return 1 + + local start now + start="$(date +%s)" + while true; do + if [[ -s "$out" ]]; then + # Remove the prompt notification as soon as we got the answer + if have termux-notification-remove; then + termux-notification-remove "$nid" >/dev/null 2>&1 || true + fi + tr -d '\r\n' < "$out" + return 0 + fi + now="$(date +%s)" + if (( now - start >= TIMEOUT_SECS )); then + # Remove the prompt notification on timeout too + if have termux-notification-remove; then + termux-notification-remove "$nid" >/dev/null 2>&1 || true + fi + return 1 + fi + sleep 1 + done +} + +ask_port_5digits() { + # args: key title + local key="$1" title="$2" v="" + while true; do + v="$(notify_ask_one "$key" "$title" "(5 digits)")" || return 1 + v="${v//[[:space:]]/}" + [[ "$v" =~ ^[0-9]{5}$ ]] || continue + echo "$v" + return 0 + done +} + +ask_code_6digits() { + local v="" + while true; do + v="$(notify_ask_one code "PAIR CODE" "(6 digits)")" || return 1 + v="${v//[[:space:]]/}" + [[ -n "$v" ]] || continue + [[ "$v" =~ ^[0-9]+$ ]] || continue + # Normalize to 6 digits (allow missing leading zeros) + if ((${#v} < 6)); then + v="$(printf "%06d" "$v")" + fi + [[ "$v" =~ ^[0-9]{6}$ ]] || continue + echo "$v" + return 0 + done +} + +cleanup_offline_loopback() { + local keep_serial="$1" # e.g. 127.0.0.1:41313 + local serial state rest + while read -r serial state rest; do + [[ -n "${serial:-}" ]] || continue + [[ "$serial" == ${HOST}:* ]] || continue + [[ "$state" == "offline" ]] || continue + [[ "$serial" == "$keep_serial" ]] && continue + adb disconnect "$serial" >/dev/null 2>&1 || true + done < <(adb devices 2>/dev/null | tail -n +2 | sed '/^[[:space:]]*$/d') +} + +# ------------------------- +# ADB wireless pair/connect wizard +# ------------------------- +adb_pair_connect() { + need adb || die "Missing adb. Install: pkg install android-tools" + + # Only require Termux:API when we will prompt the user + if [[ "$ONLY_CONNECT" != "1" || -z "${CONNECT_PORT:-}" ]]; then + termux_api_ready || die "Termux:API not ready." + fi + + echo "[*] adb: $(adb version | head -n 1)" + adb start-server >/dev/null 2>&1 || true + + if [[ "$ONLY_CONNECT" == "1" ]]; then + if [[ -n "$CONNECT_PORT" ]]; then + CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}" + [[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid CONNECT PORT (must be 5 digits): '$CONNECT_PORT'" + else + echo "[*] Asking CONNECT PORT..." + CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT." + fi + + local serial="${HOST}:${CONNECT_PORT}" + adb disconnect "$serial" >/dev/null 2>&1 || true + echo "[*] adb connect $serial" + adb connect "$serial" >/dev/null || die "adb connect failed to $serial. Verify Wireless debugging is enabled and CONNECT PORT is correct." + + if [[ "$CLEANUP_OFFLINE" == "1" ]]; then + cleanup_offline_loopback "$serial" + fi + + echo "[*] Devices:" + adb devices -l + + echo "[*] ADB check (shell):" + adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; id' || true + + cleanup_notif + ok "ADB connected (connect-only): $serial" + return 0 + fi + + if [[ -n "$CONNECT_PORT" ]]; then + CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}" + [[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'" + else + echo "[*] Asking CONNECT PORT..." + CONNECT_PORT="$(ask_port_5digits connect "CONNECT PORT")" || die "Timeout waiting CONNECT PORT." + fi + + echo "[*] Asking PAIR PORT..." + local pair_port + pair_port="$(ask_port_5digits pair "PAIR PORT")" || die "Timeout waiting PAIR PORT." + + echo "[*] Asking PAIR CODE..." + local code + code="$(ask_code_6digits)" || die "Timeout waiting PAIR CODE." + + local serial="${HOST}:${CONNECT_PORT}" + adb disconnect "$serial" >/dev/null 2>&1 || true + + echo "[*] adb pair ${HOST}:${pair_port}" + printf '%s\n' "$code" | adb pair "${HOST}:${pair_port}" || die "adb pair failed. Verify PAIR PORT and PAIR CODE (and that the pairing dialog is showing)." + + echo "[*] adb connect $serial" + adb connect "$serial" >/dev/null || die "adb connect failed after pairing. Re-check CONNECT PORT and Wireless debugging." + + if [[ "$CLEANUP_OFFLINE" == "1" ]]; then + cleanup_offline_loopback "$serial" + fi + + echo "[*] Devices:" + adb devices -l + + echo "[*] ADB check (shell):" + adb -s "$serial" shell sh -lc 'echo "it worked: adb shell is working"; getprop ro.product.model; getprop ro.build.version.release' || true + + cleanup_notif + ok "ADB connected: $serial" +} + +# Return state for an exact serial (e.g. "device", "offline", empty) +adb_device_state() { + local s="$1" + adb devices 2>/dev/null | awk -v s="$s" 'NR>1 && $1==s {print $2; exit}' +} + +# Return first loopback serial in "device" state (e.g. 127.0.0.1:41313) +adb_any_loopback_device() { + adb devices 2>/dev/null | awk -v h="$HOST" 'NR>1 && $2=="device" && index($1, h":")==1 {print $1; exit}' +} + +# Pick the loopback serial we will operate on: +# - If CONNECT_PORT is set, require that exact HOST:PORT to be in "device" state. +# - Otherwise, return the first loopback device. +adb_pick_loopback_serial() { + if [[ -n "${CONNECT_PORT:-}" ]]; then + local p="${CONNECT_PORT//[[:space:]]/}" + [[ "$p" =~ ^[0-9]{5}$ ]] || return 1 + local target="${HOST}:${p}" + [[ "$(adb_device_state "$target")" == "device" ]] && { echo "$target"; return 0; } + return 1 + fi + adb_any_loopback_device +} + +# If already connected, avoid re-pairing/re-connecting prompts (useful for --all), +# BUT only consider loopback/target connections as "already connected". +adb_pair_connect_if_needed() { + need adb || die "Missing adb. Install: pkg install android-tools" + adb start-server >/dev/null 2>&1 || true + + local serial="" + + # If user provided a connect-port, insist on that exact target serial. + if [[ -n "${CONNECT_PORT:-}" ]]; then + CONNECT_PORT="${CONNECT_PORT//[[:space:]]/}" + [[ "$CONNECT_PORT" =~ ^[0-9]{5}$ ]] || die "Invalid --connect-port (must be 5 digits): '$CONNECT_PORT'" + + local target="${HOST}:${CONNECT_PORT}" + + if [[ "$(adb_device_state "$target")" == "device" ]]; then + ok "ADB already connected to target: $target (skipping pair/connect)." + return 0 + fi + + # Try connect-only first (in case it was already paired before) + adb connect "$target" >/dev/null 2>&1 || true + if [[ "$(adb_device_state "$target")" == "device" ]]; then + ok "ADB connected to target: $target (connect-only succeeded; skipping pair)." + return 0 + fi + + # Not connected: run full wizard (pair+connect) + adb_pair_connect + return $? + fi + + # No explicit port: only skip if we already have a loopback device connected. + if serial="$(adb_any_loopback_device 2>/dev/null)"; then + ok "ADB already connected (loopback): $serial (skipping pair/connect)." + return 0 + fi + + adb_pair_connect +} + +require_adb_connected() { + need adb || { warn_red "Missing adb. Install: pkg install android-tools"; return 1; } + adb start-server >/dev/null 2>&1 || true + if ! adb_pick_loopback_serial >/dev/null 2>&1; then + warn_red "No ADB device connected." + warn "If already paired before: run --connect-only [PORT]." + warn "Otherwise: run --adb-only to pair+connect." + return 1 + fi + return 0 +} + +adb_loopback_serial_or_die() { + local s + s="$(adb_pick_loopback_serial 2>/dev/null)" || return 1 + echo "$s" +} + +# ------------------------- +# PPK / phantom-process tuning (best-effort) +# ------------------------- +ppk_fix_via_adb() { + need adb || die "Missing adb. Install: pkg install android-tools" + + local serial + if ! serial="$(adb_pick_loopback_serial)"; then + CHECK_NO_ADB=1 + warn "No ADB loopback device connected (expected ${HOST}:${CONNECT_PORT:-*})." + return 1 + fi + ok "Using ADB device: $serial" + + log "Setting PPK: max_phantom_processes=256" + # Some Android versions may ignore/rename this; we don't hard-fail. + adb -s "$serial" shell sh -lc ' + set -e + # Persist device_config changes if supported + if command -v device_config >/dev/null 2>&1; then + device_config set_sync_disabled_for_tests persistent >/dev/null 2>&1 || true + device_config put activity_manager max_phantom_processes 256 || true + echo "dumpsys effective max_phantom_processes:" + dumpsys activity settings 2>/dev/null | grep -i "max_phantom_processes=" | head -n 1 || true + else + echo "device_config not found; skipping." + fi + ' || true + + ok "PPK set done (best effort)." + return 0 +} + +# Prefer Android 14+ feature-flag override if present: +# OFF -> true, ON -> false +adb_get_child_restrictions_flag() { + local serial="$1" + adb -s "$serial" shell getprop persist.sys.fflag.override.settings_enable_monitor_phantom_procs \ + 2>/dev/null | tr -d '\r' || true +} + +# ------------------------- +# Check readiness (best-effort) +# ------------------------- +check_readiness() { + # Reset exported check signals so final_advice() never sees stale values + CHECK_NO_ADB=0 + CHECK_SDK="" + CHECK_MON="" + CHECK_PPK="" + + need adb || die "Missing adb. Install: pkg install android-tools" + adb start-server >/dev/null 2>&1 || true + + local serial + if ! serial="$(adb_pick_loopback_serial)"; then + CHECK_NO_ADB=1 + # Best-effort: keep local SDK so final_advice can still warn on A12-13. + CHECK_SDK="${ANDROID_SDK:-}" + warn_red "No ADB device connected. Cannot run checks." + warn "If already paired before: run --connect-only [PORT]." + warn "Otherwise: run --adb-only to pair+connect." + return 1 + fi + + ok "Check using ADB device: $serial" + + local dev_enabled sdk rel mon mon_fflag ds ppk_eff + sdk="$(adb -s "$serial" shell getprop ro.build.version.sdk 2>/dev/null | tr -d '\r' || true)" + rel="$(adb -s "$serial" shell getprop ro.build.version.release 2>/dev/null | tr -d '\r' || true)" + dev_enabled="$(adb -s "$serial" shell settings get global development_settings_enabled 2>/dev/null | tr -d '\r' || true)" + mon_fflag="$(adb_get_child_restrictions_flag "$serial")" + if [[ "$mon_fflag" == "true" || "$mon_fflag" == "false" ]]; then + mon="$mon_fflag" + else + mon="$(adb -s "$serial" shell settings get global settings_enable_monitor_phantom_procs 2>/dev/null | tr -d '\r' || true)" + fi + + # Get effective value from dumpsys (device_config get may return 'null' even when an effective value exists) + ds="$(adb -s "$serial" shell dumpsys activity settings 2>/dev/null | tr -d '\r' || true)" + ppk_eff="$(printf '%s\n' "$ds" | awk -F= '/max_phantom_processes=/{print $2; exit}' | tr -d '[:space:]' || true)" + + # Export check signals for the final advice logic + CHECK_SDK="${sdk:-}" + CHECK_MON="${mon:-}" + CHECK_PPK="${ppk_eff:-}" + + log " Android release=${rel:-?} sdk=${sdk:-?}" + + if [[ "${dev_enabled:-}" == "1" ]]; then + ok " Developer options: enabled (development_settings_enabled=1)" + elif [[ -n "${dev_enabled:-}" ]]; then + warn " Developer options: unknown/disabled (development_settings_enabled=${dev_enabled})" + else + warn " Developer options: unreadable (permission/ROM differences)." + fi + + # Android 14+ only: "Disable child process restrictions" proxy flag + if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then + if [[ "${mon:-}" == "false" ]]; then + ok " Child restrictions: OK (monitor=false)" + elif [[ "${mon:-}" == "true" ]]; then + warn " Child restrictions: NOT OK (monitor=true)" + elif [[ -n "${mon:-}" && "${mon:-}" != "null" ]]; then + warn " Child restrictions: unknown (${mon})" + else + warn " Child restrictions: unreadable/absent" + fi + fi + + # Android 12-13 only: PPK matters (use effective value from dumpsys) + if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then + if [[ "${ppk_eff:-}" =~ ^[0-9]+$ ]]; then + if (( ppk_eff >= 256 )); then + ok " PPK: OK (max_phantom_processes=${ppk_eff})" + else + warn " PPK: low (max_phantom_processes=${ppk_eff}) -> suggest: run --ppk-only" + fi + else + warn " PPK: unreadable (dumpsys max_phantom_processes='${ppk_eff:-}')." + fi + fi + + log " dumpsys (phantom-related):" + printf '%s\n' "$ds" | grep -i phantom || true + + if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then + log " Note: On A14+, max_phantom_processes is informational; rely on Child restrictions." + fi + if [[ "${sdk:-}" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${mon:-}" == "false" ]]; then + log " Child restrictions OK." + fi + return 0 +} + +# ------------------------- +# Self-check +# ------------------------- +self_check() { + log "Self-check summary:" + log " Android release=${ANDROID_REL:-?} sdk=${ANDROID_SDK:-?}" + + if have proot-distro; then + log " proot-distro: present" + log " proot-distro list:" + proot-distro list 2>/dev/null | sed 's/^/ /' || true + if debian_exists; then ok " Debian: present"; else warn " Debian: not present"; fi + else + warn " proot-distro: not present" + fi + + if have adb; then + log " adb: present" + adb devices -l 2>/dev/null | sed 's/^/ /' || true + local serial + if serial="$(adb_pick_loopback_serial 2>/dev/null)"; then + log " adb shell id (first device):" + adb -s "$serial" shell id 2>/dev/null | sed 's/^/ /' || true + fi + else + warn " adb: not present" + fi + + if have termux-wake-lock; then ok " Termux:API wakelock: available"; else warn " Termux:API wakelock: not available"; fi + if have termux-notification; then ok " Termux:API notifications: command present"; else warn " Termux:API notifications: missing"; fi +} +final_advice() { + # 1) Android-related warnings (only meaningful if we attempted checks) + local sdk="${CHECK_SDK:-${ANDROID_SDK:-}}" + + if [[ "${CHECK_NO_ADB:-0}" -eq 1 ]]; then + # If we could not check, still warn on A12-13 because PPK is critical there + if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then + warn "A12-13: verify PPK=256 before installing IIAB." + fi + else + # A14+ child restrictions proxy (only if readable) + if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )) && [[ "${CHECK_MON:-}" == "true" ]]; then + warn "A14+: disable child process restrictions before installing IIAB." + fi + + # Only warn about PPK on A12-13 (A14+ uses child restrictions) + if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then + if [[ "${CHECK_PPK:-}" =~ ^[0-9]+$ ]] && (( CHECK_PPK < 256 )); then + warn "PPK is low (${CHECK_PPK}); consider --ppk-only." + fi + fi + fi + + # 2) Debian “next step” should only be shown for modes that actually bootstrap Debian + case "$MODE" in + baseline|with-adb|all) + if debian_exists; then + ok "Next: proot-distro login debian" + else + warn "Debian not present. Run: proot-distro install debian" + fi + ;; + *) + # adb-only/connect-only/ppk-only/check: do not suggest Debian login as a generic ending + ;; esac } -rish_export_available_in_home() { - [[ -f "$HOME/rish" ]] && ls -1 "$HOME"/rish*.dex >/dev/null 2>&1 +# ------------------------- +# Args +# ------------------------- +set_mode() { + local new="$1" + if [[ "$MODE_SET" -eq 1 ]]; then + die "Modes are mutually exclusive. Already set: --${MODE}. Tried: --${new}" + fi + MODE="$new" + MODE_SET=1 } -install_rish_to_path_if_available() { - if ! rish_export_available_in_home; then - return 1 - fi - - local dex PREFIX_BIN - dex="$(ls -1 "$HOME"/rish*.dex 2>/dev/null | head -n1 || true)" - PREFIX_BIN="/data/data/com.termux/files/usr/bin" - - install -m 0755 "$HOME/rish" "$PREFIX_BIN/rish" - install -m 0644 "$dex" "$PREFIX_BIN/$(basename "$dex")" - # Typical adjustment required by rish - sed -i 's/PKG/com.termux/g' "$PREFIX_BIN/rish" || true - - if [[ "$CLEANUP_RISH_EXPORT" -eq 1 ]]; then - warn "Cleanup requested: removing rish exports from HOME (~/rish, ~/rish*.dex)." - rm -f "$HOME/rish" "$HOME"/rish*.dex || true - fi - return 0 -} - -run_rish() { - local out - out="$(rish -c "$1" 2>&1)" || { - printf "%s\n" "$out" - return 1 - } - printf "%s\n" "$out" - return 0 -} +while [[ $# -gt 0 ]]; do + case "$1" in + --with-adb) set_mode "with-adb"; shift ;; + --adb-only) set_mode "adb-only"; shift ;; + --connect-only) + set_mode "connect-only" + ONLY_CONNECT=1 + # Optional positional port (5 digits) + if [[ "${2:-}" =~ ^[0-9]{5}$ ]]; then + CONNECT_PORT="$2" + shift 2 + else + shift + fi + ;; + --ppk-only) set_mode "ppk-only"; shift ;; + --check) set_mode "check"; shift ;; + --all) set_mode "all"; shift ;; + --connect-port) CONNECT_PORT="${2:-}"; shift 2 ;; + --timeout) TIMEOUT_SECS="${2:-180}"; shift 2 ;; + --host) HOST="${2:-127.0.0.1}"; shift 2 ;; + --reset-debian|--clean-debian) RESET_DEBIAN=1; shift ;; + --no-log) LOG_ENABLED=0; shift ;; + --log-file) LOG_FILE="${2:-}"; shift 2 ;; + --debug) DEBUG=1; shift ;; + -h|--help) usage; exit 0 ;; + *) shift ;; + esac +done # ------------------------- -# LAST STEP: Android 12/13 PPK fix via rish -# ------------------------- -step_ppk_fix_android_12_13() { - local major rel sdk - major="$(android_major_12_to_13)" - rel="${ANDROID_REL:-?}" - sdk="${ANDROID_SDK:-?}" - - if [[ -z "$major" ]]; then - ok "Android release=$rel sdk=$sdk -> PPK fix not applicable (only Android 12/13)." - return 0 - fi - - log "Android $major (release=$rel sdk=$sdk) -> PPK fix target: max_phantom_processes=256" - - if ! have rish; then - install_rish_to_path_if_available || true - fi - - if ! have rish; then - warn_red "PPK fix could not be applied: rish is not available." - warn_red "Start Shizuku, then export 'rish' and the matching .dex into Termux (SAF)." - warn_red "Continuing without changing PPK." - return 0 - fi - - log "Current phantom setting:" - local cur - cur="$(run_rish "dumpsys activity settings | grep -i phantom || true" || true)" - if echo "$cur" | grep -qi "Server is not running"; then - warn_red "Shizuku/rish server is not running (or not authorized)." - warn_red "Open Shizuku -> start the service -> authorize rish, then re-run:" - warn_red " ./0_termux-setup.sh --ppk-only" - return 0 - fi - printf "%s\n" "$cur" - - local target=256 - log "Applying: device_config put activity_manager max_phantom_processes $target" - local apply - apply="$(run_rish "device_config put activity_manager max_phantom_processes $target" || true)" - if echo "$apply" | grep -qi "Server is not running"; then - warn_red "Shizuku/rish server is not running (or not authorized)." - warn_red "Open Shizuku -> start the service -> authorize rish, then re-run:" - warn_red " ./0_termux-setup.sh --ppk-only" - return 0 - fi - - log "Final phantom setting:" - run_rish "dumpsys activity settings | grep -i phantom || true" || true - - ok "PPK fix applied (or re-applied)." -} - -# ------------------------- -# Self-check summary -# ------------------------- -self_check() { - local rel sdk major - rel="${ANDROID_REL:-?}" - sdk="${ANDROID_SDK:-?}" - major="$(android_major_12_to_13)" - - log "Self-check summary:" - log " Android release=$rel sdk=$sdk" - - if have proot-distro; then - log " proot-distro: present" - log " proot-distro list:" - proot-distro list 2>/dev/null | sed 's/^/ /' || true - if debian_exists; then - ok " Debian: present" - else - warn " Debian: not present" - fi - else - warn " proot-distro: not present" - fi - - if rish_export_available_in_home; then - ok " rish export: present in HOME (~/rish, ~/rish*.dex)" - else - warn " rish export: not found in HOME" - fi - - if have rish; then - ok " rish: installed in PATH" - log " rish -c id:" - run_rish "id" | sed 's/^/ /' || true - if [[ -n "$major" ]]; then - log " phantom setting (via rish):" - run_rish "dumpsys activity settings | grep -i phantom || true" | sed 's/^/ /' || true - fi - else - warn " rish: not installed in PATH" - fi -} - -# ------------------------- -# Main +# Main flows # ------------------------- main() { + setup_logging "$@" + sanitize_timeout acquire_wakelock - step_termux_repo_select_once - step_termux_base - if [[ "$PPK_ONLY" -eq 1 ]]; then - step_ppk_fix_android_12_13 - self_check - ok "Done (--ppk-only)." - exit 0 - fi + case "$MODE" in + baseline) + step_termux_repo_select_once + step_termux_base + step_debian_bootstrap_default + ;; - step_debian_bootstrap_default + with-adb) + step_termux_repo_select_once + step_termux_base + step_debian_bootstrap_default + adb_pair_connect_if_needed + ;; - # IMPORTANT: keep this last so users can re-run it quickly. - step_ppk_fix_android_12_13 + adb-only) + step_termux_base + adb_pair_connect_if_needed + ;; + + connect-only) + step_termux_base + adb_pair_connect + ;; + + ppk-only) + # No baseline, no Debian. Requires adb already available + connected. + require_adb_connected || exit 1 + ppk_fix_via_adb || true + ;; + + check) + step_termux_base + check_readiness || true + ;; + + all) + step_termux_repo_select_once + step_termux_base + step_debian_bootstrap_default + adb_pair_connect_if_needed + + # Android 12-13 only (SDK 31-33): apply PPK tuning automatically + if [[ "${ANDROID_SDK:-}" =~ ^[0-9]+$ ]] && (( ANDROID_SDK >= 31 && ANDROID_SDK <= 33 )); then + log "Android SDK=${ANDROID_SDK} detected -> applying --ppk automatically (12-13 rule)." + ppk_fix_via_adb || true + else + log "Android SDK=${ANDROID_SDK:-?} -> skipping auto-PPK (only for Android 12-13)." + fi + check_readiness || true + ;; + + *) + die "Unknown MODE='$MODE'" + ;; + esac self_check - - ok "0_termux-setup.sh completed." - log "Tip: re-run only the PPK fix: ./0_termux-setup.sh --ppk-only" - log "Tip: clean Debian environment: ./0_termux-setup.sh --reset-debian" - ok "You can proceed with: proot-distro login debian" + ok "0_termux-setupv2.sh completed (mode=$MODE)." + log "Tip: Connect-only: ./0_termux-setupv2.sh --connect-only [PORT]" + log "Tip: Pair+connect: ./0_termux-setupv2.sh --adb-only" + log "Tip: Check: ./0_termux-setupv2.sh --check" + log "Tip: Apply PPK: ./0_termux-setupv2.sh --ppk-only" + log "Tip: Full run: ./0_termux-setupv2.sh --all" + log "Tip: Reset Debian: ./0_termux-setupv2.sh --reset-debian" + final_advice } main "$@"