Compare commits

..

13 Commits

14 changed files with 2516 additions and 713 deletions

1314
android/0_termux-setup_v2.sh Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
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; }
# -------------------------
# Global defaults (may be overridden via environment)
# -------------------------
STATE_DIR="${STATE_DIR:-${HOME}/.iiab-android}"
ADB_STATE_DIR="${ADB_STATE_DIR:-${STATE_DIR}/adbw_pair}"
LOG_DIR="${LOG_DIR:-${STATE_DIR}/logs}"
HOST="${HOST:-127.0.0.1}"
CONNECT_PORT="${CONNECT_PORT:-}"
TIMEOUT_SECS="${TIMEOUT_SECS:-180}"
# Defaults used by ADB flows / logging / misc
CLEANUP_OFFLINE="${CLEANUP_OFFLINE:-1}"
DEBUG="${DEBUG:-0}"

View File

@ -0,0 +1,76 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# 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() {
# Save original console fds so interactive tools still work after we redirect stdout/stderr.
exec 3>&1 4>&2
# 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)
local started
started="$(date -Is 2>/dev/null || date 2>/dev/null || echo "?")"
{
echo "=== iiab termux setup v2 log ==="
echo "Started: $started"
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
}

View File

@ -0,0 +1,115 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# 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[@]}" "$@"; }
# -------------------------
# 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)"
# -------------------------
# Wakelock (Termux:API)
# -------------------------
WAKELOCK_HELD=0
acquire_wakelock() {
if have termux-wake-lock; then
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: 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
}
# -------------------------
# One-time repo selector
# -------------------------
step_termux_repo_select_once() {
local stamp="$STATE_DIR/stamp.termux_repo_selected"
[[ -f "$stamp" ]] && return 0
if ! have termux-change-repo; then
warn "termux-change-repo not found; skipping mirror selection."
return 0
fi
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"
printf "[iiab] Launch termux-change-repo now? [Y/n]: " > /dev/tty
if ! read -r ans < /dev/tty; then
warn "No interactive TTY available; skipping mirror selection (run script directly to be prompted)."
return 0
fi
ans="${ans:-Y}"
if [[ "$ans" =~ ^[Yy]$ ]]; then
# Logging redirects stdout/stderr to pipes, which can break the UI.
# Run it using /dev/tty and the original console fds (3/4).
if [[ -r /dev/tty ]]; then
termux-change-repo </dev/tty >&3 2>&4 || true
else
termux-change-repo || true
fi
ok "Mirror selection completed (or skipped inside the UI)."
else
warn "Mirror selection skipped by user."
fi
date > "$stamp"
return 0
fi
warn "No /dev/tty available; skipping mirror selection."
return 0
}
# -------------------------
# Baseline packages
# -------------------------
step_termux_base() {
local stamp="$STATE_DIR/stamp.termux_base"
if [[ -f "$stamp" ]]; then
ok "Termux baseline already prepared (stamp found)."
return 0
fi
log "Updating Termux packages (noninteractive) and installing baseline dependencies..."
export DEBIAN_FRONTEND=noninteractive
termux_apt update || true
termux_apt upgrade || true
termux_apt install \
ca-certificates \
curl \
coreutils \
grep \
sed \
gawk \
openssh \
proot proot-distro \
android-tools \
termux-api \
|| true
if have proot-distro && \
have adb && have termux-notification && \
have termux-dialog; then
ok "Termux baseline ready."
date > "$stamp"
else
warn_red "Baseline incomplete (missing proot-distro/adb/termux-notification). Not stamping; rerun later."
fi
}

View File

@ -0,0 +1,79 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# Debian bootstrap
# -------------------------
debian_exists() {
have proot-distro || return 1
proot-distro login debian -- true >/dev/null 2>&1
}
ensure_proot_distro() {
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
}
proot_install_debian_safe() {
local out rc
set +e
out="$(proot-distro install debian 2>&1)"
rc=$?
set -e
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_debian_bootstrap_default() {
if ! ensure_proot_distro; then
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)..."
if proot-distro help 2>/dev/null | grep -qE '\breset\b'; then
proot-distro reset debian || true
else
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 || true
fi
fi
log "Installing minimal tools inside Debian (noninteractive)..."
if ! debian_exists; then
warn_red "Debian is not available in proot-distro (install may have failed). Rerun later."
return 0
fi
local rc=0
set +e
proot-distro login debian -- bash -lc '
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold \
install ca-certificates curl coreutils
'
rc=$?
set -e
if [[ $rc -eq 0 ]]; then
ok "Debian bootstrap complete."
else
warn_red "Debian bootstrap incomplete (inner apt-get failed, rc=$rc)."
warn "You can retry later with: proot-distro login debian"
fi
}

View File

@ -0,0 +1,129 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# Termux:API notifications + prompts
# -------------------------
NOTIF_BASE_ID=9400
NOTIF_SEQ=0
LAST_NOTIF_ID=""
# Termux:API sanity check (notifications)
termux_api_ready() {
have termux-notification || return 1
# Quick probe: some setups fail until Termux:API app is installed/allowed.
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.reply"
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
# Direct reply: Termux:API injects the user input into $REPLY for the action.
# Write it to a known file, then the main loop reads it.
local action
action="sh -lc 'umask 077; printf \"%s\" \"\$REPLY\" > \"${out}\"'"
termux-notification \
--id "$nid" \
--ongoing \
--priority max \
--title "$title" \
--content "$content" \
--sound \
--button1 "Answer" \
--button1-action "$action" \
|| return 1
local start now reply
start="$(date +%s)"
while true; do
if [[ -f "$out" ]]; then
reply="$(tr -d '\r\n' < "$out" 2>/dev/null || true)"
rm -f "$out" >/dev/null 2>&1 || true
if have termux-notification-remove; then
termux-notification-remove "$nid" >/dev/null 2>&1 || true
fi
printf '%s' "$reply"
return 0
fi
now="$(date +%s)"
if (( now - start >= TIMEOUT_SECS )); then
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
}

View File

@ -0,0 +1,272 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# -------------------------
# ADB wireless pair/connect wizard
# -------------------------
# Local stamp so we can detect "connect-only" misuse after reinstall/clear-data.
ADB_PAIRED_STAMP="${ADB_STATE_DIR}/stamp.adb_paired"
adb_hostkey_fingerprint() {
# Returns a stable fingerprint for THIS Termux install's adb host key.
local pub="${HOME}/.android/adbkey.pub"
[[ -r "$pub" ]] || return 1
if have sha256sum; then
sha256sum "$pub" | awk '{print $1}'
elif have shasum; then
shasum -a 256 "$pub" | awk '{print $1}'
elif have openssl; then
openssl dgst -sha256 "$pub" 2>/dev/null | awk '{print $2}'
elif have md5sum; then
md5sum "$pub" | awk '{print $1}'
else
return 1
fi
}
adb_stamp_write() {
# args: mode serial
local mode="$1" serial="$2" fp=""
fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
{
echo "ts=$(date -Is 2>/dev/null || date || true)"
echo "mode=${mode}"
echo "host=${HOST}"
echo "serial=${serial}"
echo "connect_port=${CONNECT_PORT:-}"
echo "hostkey_fp=${fp}"
} >"$ADB_PAIRED_STAMP" 2>/dev/null || true
chmod 600 "$ADB_PAIRED_STAMP" 2>/dev/null || true
}
adb_stamp_read_fp() {
[[ -r "$ADB_PAIRED_STAMP" ]] || return 1
sed -n 's/^hostkey_fp=//p' "$ADB_PAIRED_STAMP" 2>/dev/null | head -n 1
}
adb_warn_connect_only_if_suspicious() {
# Called only in connect-only flows.
local cur_fp old_fp
cur_fp="$(adb_hostkey_fingerprint 2>/dev/null || true)"
old_fp="$(adb_stamp_read_fp 2>/dev/null || true)"
if [[ ! -f "$ADB_PAIRED_STAMP" ]]; then
warn "connect-only assumes THIS Termux install has been paired before."
warn "No local pairing stamp found ($ADB_PAIRED_STAMP)."
warn "If you reinstalled Termux / cleared data / changed user, you must re-pair (run: --adb-only)."
[[ -n "$cur_fp" ]] && warn "Current ADB hostkey fingerprint: ${cur_fp:0:12}..."
return 0
fi
if [[ -n "$old_fp" && -n "$cur_fp" && "$old_fp" != "$cur_fp" ]]; then
warn_red "ADB host key changed since last pairing stamp."
warn "Old fingerprint: ${old_fp:0:12}... Current: ${cur_fp:0:12}..."
warn "Android Wireless debugging -> Paired devices: remove the old entry, then run: --adb-only"
fi
}
adb_connect_verify() {
# args: serial (HOST:PORT)
local serial="$1" out rc start now state
set +e
out="$(adb connect "$serial" 2>&1)"
rc=$?
set -e
# Always verify via `adb devices` (adb may exit 0 even on failure).
start="$(date +%s)"
while true; do
state="$(adb_device_state "$serial" || true)"
[[ "$state" == "device" ]] && { printf '%s\n' "$out"; return 0; }
now="$(date +%s)"
(( now - start >= 5 )) && break
sleep 1
done
warn_red "adb connect did not result in a usable device entry for: $serial (state='${state:-none}')."
warn "adb connect output: ${out:-<none>}"
warn "If you recently reinstalled Termux/cleared data, the phone may show an OLD paired device. Remove it and re-pair."
return 1
}
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_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
adb_warn_connect_only_if_suspicious
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_verify "$serial" >/dev/null || die "adb connect failed to $serial. Verify Wireless debugging is enabled, and pairing exists for THIS Termux install."
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
adb_stamp_write "connect-only" "$serial"
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_verify "$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
adb_stamp_write "paired" "$serial"
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; found=1; exit}
END { exit (found ? 0 : 1) }
'
}
# 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"
}

View File

@ -0,0 +1,192 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# PPK / phantom-process checks and tuning via ADB (best-effort)
# Moved out of 99_main.sh to keep it as an orchestrator.
# -------------------------
# 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_android_flags() {
have adb || return 0
adb start-server >/dev/null 2>&1 || true
local serial sdk rel mon mon_fflag ds ppk_eff
serial="$(adb_pick_loopback_serial 2>/dev/null)" || {
warn "ADB: no loopback device connected. Tip: run --adb-only (pair+connect) or --check for more info."
return 0
}
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)"
log " Android flags (quick): release=${rel:-?} sdk=${sdk:-?} serial=$serial"
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
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
if [[ "$mon" == "false" ]]; then
ok " Child restrictions: OK (monitor=false)"
elif [[ "$mon" == "true" ]]; then
warn " Child restrictions: NOT OK (monitor=true) -> check Developer Options"
else
warn " Child restrictions: unknown/unreadable (monitor='${mon:-}')"
fi
fi
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
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)"
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: --ppk-only"
fi
else
warn " PPK: unreadable (max_phantom_processes='${ppk_eff:-}')"
fi
fi
# Avoid redundant tip when we're already in --check mode.
if [[ "${MODE:-}" != "check" && "${MODE:-}" != "all" ]]; then
log " Tip: run --check for full details."
fi
}

View File

@ -0,0 +1,358 @@
# shellcheck shell=bash
# Module file (no shebang). Bundled by build_bundle.sh
# 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)
# -------------------------
# Defaults
# -------------------------
# NOTE: Core defaults live in 00_lib_common.sh to guarantee availability for all modules.
# Ensure state directories exist (safe even if user overrides via environment).
mkdir -p "$STATE_DIR" "$ADB_STATE_DIR" "$LOG_DIR" 2>/dev/null || true
RESET_DEBIAN=0
ONLY_CONNECT=0
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
CONNECT_PORT_FROM="" # "", "flag", "positional"
usage() {
cat <<'EOF'
Usage:
./0_termux-setupv2.sh
-> Termux baseline + Debian bootstrap (idempotent). No ADB prompts.
./0_termux-setupv2.sh --with-adb
-> Termux baseline + Debian bootstrap + ADB pair/connect if needed (skips if already connected).
./0_termux-setupv2.sh --adb-only [--connect-port PORT]
-> Only ADB pair/connect if needed (no Debian; skips if already connected).
Tip: --connect-port skips the CONNECT PORT prompt (youll still be asked for PAIR PORT + PAIR CODE).
./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 used with --adb-only
--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:
- 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
}
trap 'cleanup_notif >/dev/null 2>&1 || true; release_wakelock >/dev/null 2>&1 || true' EXIT INT TERM
# NOTE: Termux:API prompts live in 40_mod_termux_api.sh
# -------------------------
# 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
# renable in need for verbose output.
# 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
# Quick Android flags check (best-effort; no prompts)
self_check_android_flags || true
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:-}}"
local adb_connected=0
local serial="" mon="" mon_fflag=""
# Best-effort: detect whether an ADB loopback device is already connected.
# (We do NOT prompt/pair here; we only check current state.)
if have adb; then
adb start-server >/dev/null 2>&1 || true
if adb_pick_loopback_serial >/dev/null 2>&1; then
adb_connected=1
serial="$(adb_pick_loopback_serial 2>/dev/null || true)"
fi
fi
# Baseline safety gate:
# On Android 12-13 (SDK 31-33), IIAB/proot installs can fail if PPK is low (often 32).
# Baseline mode does NOT force ADB pairing nor run check_readiness(), so PPK may be unknown.
# If PPK is not determined, suggest running --all BEFORE telling user to proceed to proot-distro.
if [[ "$MODE" == "baseline" ]]; then
if [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 31 && sdk <= 33 )); then
# If we didn't run checks, CHECK_PPK will be empty. Even with adb_connected=1, baseline
# still doesn't populate CHECK_PPK unless user ran --check/--all.
if [[ "${CHECK_PPK:-}" != "" && "${CHECK_PPK:-}" =~ ^[0-9]+$ ]]; then
: # PPK determined -> ok to continue with normal advice below
else
warn "Android 12-13: PPK value hasn't been verified (max_phantom_processes may be low, e.g. 32)."
warn "Before starting the IIAB install, run the complete setup so it can apply/check PPK=256; otherwise the installation may fail:"
ok " ./0_termux-setupv2.sh --all"
return 0
fi
elif [[ "$sdk" =~ ^[0-9]+$ ]] && (( sdk >= 34 )); then
# On Android 14+, rely on "Disable child process restrictions"
# Proxy signals: settings_enable_monitor_phantom_procs (or the fflag override).
# Baseline does not run check_readiness(), so CHECK_MON is usually empty.
if [[ "${CHECK_MON:-}" == "false" ]]; then
: # Verified OK (rare in baseline) -> continue
else
# If ADB is already connected, try to read the flag best-effort (no prompts).
if [[ "$adb_connected" -eq 1 && -n "${serial:-}" ]]; then
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
fi
if [[ "${mon:-}" == "false" ]]; then
: # Restrictions already disabled -> ok to continue
else
if [[ "${mon:-}" == "true" ]]; then
warn "Android 14+: child process restrictions appear ENABLED (monitor=true)."
else
warn "Android 14+: child process restrictions haven't been verified (monitor flag unreadable/unknown)."
fi
warn "Before starting the IIAB install, run the complete setup (--all) so it can guide you to verify such setting; otherwise the installation may fail:"
ok " ./0_termux-setupv2.sh --all"
return 0
fi
fi
fi
fi
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
}
# -------------------------
# 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
}
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
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "positional" ]] && \
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
CONNECT_PORT="$2"
CONNECT_PORT_FROM="positional"
shift 2
else
shift
fi
;;
--ppk-only) set_mode "ppk-only"; shift ;;
--check) set_mode "check"; shift ;;
--all) set_mode "all"; shift ;;
--connect-port)
[[ -n "${CONNECT_PORT_FROM:-}" && "${CONNECT_PORT_FROM}" != "flag" ]] && \
die "CONNECT PORT specified twice (positional + --connect-port). Use only one."
CONNECT_PORT="${2:-}"
CONNECT_PORT_FROM="flag"
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
validate_args() {
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'"
case "$MODE" in
adb-only|with-adb|connect-only|ppk-only|check|all) : ;;
baseline)
log "--connect-port requires an ADB mode."
die "Use along with: --adb-only / --with-adb / --connect-only / --check / --ppk-only / --all"
;;
*)
die "--connect-port is not valid with mode=$MODE"
;;
esac
fi
}
# -------------------------
# Main flows
# -------------------------
main() {
setup_logging "$@"
validate_args
sanitize_timeout
acquire_wakelock
case "$MODE" in
baseline)
step_termux_repo_select_once
step_termux_base
step_debian_bootstrap_default
;;
with-adb)
step_termux_repo_select_once
step_termux_base
step_debian_bootstrap_default
adb_pair_connect_if_needed
;;
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-setupv2.sh completed (mode=$MODE)."
log "---- Mode list ----"
log "Connect-only --connect-only [PORT]"
log "Pair+connect --adb-only [--connect-port PORT]"
log "Check --check"
log "Apply PPK --ppk-only"
log "Base+Debian+Pair+connect --with-adb"
log "Full run --all"
log "Reset Debian --reset-debian"
log "-------------------"
final_advice
}
main "$@"

View File

@ -0,0 +1,27 @@
# termux-setup modules
This project is maintained as multiple Bash "modules" that are bundled into a single script:
`0_termux-setup_v2.sh` (the file served for: `curl ... | bash`).
## Rules
- Modules MUST NOT include a shebang (`#!...`).
- Modules SHOULD NOT run top-level code (prefer functions), except `99_main.sh`.
- Do not add `set -euo pipefail` in modules (the bundle already sets it once).
- Keep module names stable and ordered via `manifest.sh`.
Recommended header for every module:
```
# termux-setup module.
# DO NOT add a shebang or "set -euo pipefail" here.
# Keep only function/variable definitions (no top-level execution).
# See: termux-setup/README.md
```
## Rebuild:
```
cd termux-setup
bash build_bundle.sh
```

View File

@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail
# build_bundle.sh
# Bundles modules listed in manifest.sh into ../0_termux-setup_v2.sh
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$SCRIPT_DIR"
MANIFEST="${ROOT_DIR}/manifest.sh"
[[ -f "$MANIFEST" ]] || { echo "[!] Missing manifest.sh at: $MANIFEST" >&2; exit 1; }
# Modules live next to build_bundle.sh (same dir)
MOD_DIR="$ROOT_DIR"
PARENT_DIR="$(cd -- "${ROOT_DIR}/.." && pwd)"
OUT_DIR="${ROOT_DIR}/dist"
OUT_FILE="${PARENT_DIR}/0_termux-setup_v2.sh"
TMP_FILE="${OUT_DIR}/.0_termux-setup_v2.tmp.$RANDOM$RANDOM"
mkdir -p "$OUT_DIR"
# Load MODULES array
# shellcheck source=/dev/null
source "$MANIFEST"
# With "set -u", don't reference an unset array directly.
if ! declare -p MODULES >/dev/null 2>&1; then
echo "[!] manifest.sh did not define MODULES array." >&2
exit 1
fi
if (( ${#MODULES[@]} < 1 )); then
echo "[!] MODULES array is empty in manifest.sh." >&2
exit 1
fi
cleanup_tmp() { rm -f -- "$TMP_FILE" 2>/dev/null || true; }
trap cleanup_tmp EXIT
# Bundle header
{
echo '#!/data/data/com.termux/files/usr/bin/bash'
echo 'set -euo pipefail'
echo
echo '# -----------------------------------------------------------------------------'
echo '# GENERATED FILE: do not edit directly.'
echo '# Source modules: termux-setup/*.sh + manifest.sh'
echo '# Rebuild: (cd termux-setup && bash build_bundle.sh)'
echo '# -----------------------------------------------------------------------------'
echo
} >"$TMP_FILE"
# Append each module
for mod in "${MODULES[@]}"; do
src="${MOD_DIR}/${mod}"
if [[ ! -f "$src" ]]; then
echo "[!] Missing module: $src" >&2
exit 1
fi
# Disallow standalone scripts: modules must not have a shebang
if head -n 1 "$src" | grep -q '^#!'; then
echo "[!] Module must NOT include a shebang: $src" >&2
exit 1
fi
{
echo
echo "# ---- BEGIN ${mod} ----"
cat "$src"
echo
echo "# ---- END ${mod} ----"
echo
} >>"$TMP_FILE"
done
# Ensure final newline
printf '\n' >>"$TMP_FILE"
# Install bundle atomically
chmod 700 "$TMP_FILE" 2>/dev/null || true
mv -f -- "$TMP_FILE" "$OUT_FILE"
chmod 700 "$OUT_FILE" 2>/dev/null || true
echo "[ok] Wrote: $OUT_FILE"

View File

@ -0,0 +1,13 @@
# termux-setup manifest
# Order matters: later modules can rely on functions/vars defined earlier.
MODULES=(
"00_lib_common.sh"
"10_mod_logging.sh"
"20_mod_termux_base.sh"
"30_mod_debian.sh"
"40_mod_termux_api.sh"
"50_mod_adb.sh"
"60_mod_ppk_checks.sh"
"99_main.sh"
)

View File

@ -52,7 +52,7 @@ LOCAL_VARS_URL="${LOCAL_VARS_URL:-https://raw.githubusercontent.com/iiab/iiab/re
WAIT_TRIES="${WAIT_TRIES:-60}" # used ONLY for the first auto-resume
WAIT_SLEEP="${WAIT_SLEEP:-5}"
STAGGER="${STAGGER:-20}"
STAGGER="${STAGGER:-15}"
ACTION="run"
modules=()
@ -149,7 +149,7 @@ fi
# Default module
if [[ "${#modules[@]}" -eq 0 ]]; then
modules=("")
modules=("code")
fi
# If no --pr provided, take from env IIAB_PR (space-separated)
@ -174,7 +174,6 @@ fi
LOGROOT="${LOGROOT:-iiab_multipass_runs_$(date +%Y%m%d)}"
mkdir -p "$LOGROOT"
echo "[INFO] Logging files stored at: $LOGROOT"
stamp() { date +%H%M%S; }
@ -333,6 +332,7 @@ run_install_txt() {
log="$LOGROOT/${vm}.install.${t}.log"
rc="$LOGROOT/${vm}.install.${t}.rc"
echo "[INFO] Logging files stored at: $LOGROOT"
echo "[INFO] install.txt in $vm (log $(basename "$log")) ..."
local modules_str pr_str
@ -392,22 +392,8 @@ run_install_txt() {
fi
echo "--- install.txt ---"
INSTALL_PRIMARY="https://iiab.io/install.txt"
INSTALL_FALLBACK="https://raw.githubusercontent.com/iiab/iiab-factory/refs/heads/master/install.txt"
CURL="curl -fsSL --retry 5 --retry-delay 2 --connect-timeout 15"
tmp_install="$(mktemp)"
if $CURL "$INSTALL_PRIMARY" -o "$tmp_install"; then
:
else
echo "Warning: failed to fetch $INSTALL_PRIMARY"
echo "Falling back to $INSTALL_FALLBACK"
$CURL "$INSTALL_FALLBACK" -o "$tmp_install"
fi
echo "bash -s -- ${install_args[*]} < \"$tmp_install\""
bash -s -- "${install_args[@]}" < "$tmp_install"
rm -f "$tmp_install"
echo "curl -fsSL https://iiab.io/install.txt | bash -s -- ${install_args[*]}"
curl -fsSL https://iiab.io/install.txt | bash -s -- "${install_args[@]}"
echo "--- install done ---"
' >"$log" 2>&1
@ -505,33 +491,6 @@ phase_parallel_stagger() {
set -e
}
pipeline_parallel_stagger() {
# Per-VM pipeline: each VM does launch->install->resume independently.
# This avoids "install barrier" where slow Debian blocks Ubuntu's resume.
local pids=()
for i in "${!names[@]}"; do
local vm="${names[$i]}"
(
sleep $((i * STAGGER))
launch_one "$vm"
run_install_txt "$vm"
# Only Ubuntu tends to reboot during install; Debian often doesn't.
local waitflag=1
if [[ "${VM_IMAGE[$vm]}" == "$DEBIAN13_IMAGE_URL" ]]; then
waitflag=0
fi
resume_iiab "$vm" "$waitflag"
) &
pids+=("$!")
done
# Don't abort the whole script if one VM fails; we still want logs + summary.
set +e
wait_all "${pids[@]}"
set -e
}
# ---- Main ----
build_vm_lists
@ -546,7 +505,9 @@ fi
case "$ACTION" in
run)
pipeline_parallel_stagger
phase_parallel_stagger launch
phase_parallel_stagger install
phase_parallel_stagger resume 1
summary
;;
continue)

View File

@ -1,397 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# -----------------------------------------------------------------------------
# cotg_curl_stats.ansiblelike.sh
#
# Stress-test helper that mimics the Ansible playbook logic:
# - "uri" to fetch the APK index with retries/delay/until
# - optional fallback "uri" to a WP JSON endpoint (best-effort)
# - (optional) "get_url"-like APK download with retries, without *.apk.1 files
#
# Positional args (kept compatible with your original script):
# 1: N (default: 300)
# 2: SLEEP_SEC (default: 0.5)
#
# Environment variables:
# UA=ansible-httpget
# RETRIES=5
# DELAY=5
# JSON_URL=... # like code_fetch_apk_url_json (optional)
# DOWNLOAD_BASE=... # like code_download_url (optional, used if DO_DOWNLOAD=1)
# DO_DOWNLOAD=0|1 # if 1, download the latest armv8a APK each loop
# DOWNLOAD_DIR=... # where to store downloaded APKs (default: tmpdir/apk)
# DOWNLOAD_TIMEOUT=60 # curl max-time
# CURL_HTTP=--http1.1 # force HTTP version (Ansible's urllib is typically HTTP/1.1)
# CURL_EXTRA_ARGS="" # extra curl args (space-separated)
# -----------------------------------------------------------------------------
URL="https://www.appdevforall.org/codeonthego/"
N="${1:-500}"
SLEEP_SEC="${2:-0.5}"
UA="${UA:-ansible-httpget}"
RETRIES="${RETRIES:-5}"
DELAY="${DELAY:-5}"
JSON_URL="${JSON_URL:-https://www.appdevforall.org/wp-json/wp/v2/pages/2223}"
DOWNLOAD_BASE="${DOWNLOAD_BASE:-}"
DO_DOWNLOAD="${DO_DOWNLOAD:-0}"
DOWNLOAD_TIMEOUT="${DOWNLOAD_TIMEOUT:-60}"
CURL_HTTP="${CURL_HTTP:---http1.1}"
CURL_EXTRA_ARGS="${CURL_EXTRA_ARGS:-}"
# Patterns (defaults aligned to the playbook vars)
APK_RE="${APK_RE:-CodeOnTheGo-.*?armv8a\\.apk}"
CF_RE='(cloudflare|cf-chl|just a moment|attention required)'
# "uri" logic in your playbook treats these as terminal statuses for the index fetch
ALLOWED_INDEX_STATUS=(200 403 404)
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
DOWNLOAD_DIR="${DOWNLOAD_DIR:-$tmpdir/apk}"
mkdir -p "$DOWNLOAD_DIR"
# Stats
declare -A status_count=()
declare -A apk_count=()
declare -A cf_count=()
declare -A blocked_count=()
declare -A retry_hist_index=()
declare -A retry_hist_json=()
declare -A retry_hist_apk=()
min_size=""
max_size=""
sum_size=0
note_size() {
local sz="$1"
[[ -z "$min_size" || "$sz" -lt "$min_size" ]] && min_size="$sz"
[[ -z "$max_size" || "$sz" -gt "$max_size" ]] && max_size="$sz"
sum_size=$((sum_size + sz))
}
in_list() {
local x="$1"; shift
local v
for v in "$@"; do
[[ "$x" == "$v" ]] && return 0
done
return 1
}
# curl -> files, return status code (0 means "undefined" like Ansible when status missing)
# Writes headers/body even if status is 0.
curl_fetch() {
local url="$1" body="$2" hdr="$3" accept_header="${4:-}"
local rc status
local -a extra
# Allow tuning curl behavior to get closer to what Ansible's urllib does.
# (Example: force HTTP/1.1 instead of opportunistic HTTP/2.)
extra=("$CURL_HTTP")
if [[ -n "$CURL_EXTRA_ARGS" ]]; then
# shellcheck disable=SC2206
extra+=( $CURL_EXTRA_ARGS )
fi
: >"$hdr"
: >"$body"
# Disable -e locally to collect rc + still return 0 status on failures
set +e
if [[ -n "$accept_header" ]]; then
curl -sS -L -A "$UA" -m "$DOWNLOAD_TIMEOUT" "${extra[@]}" -H "Accept: $accept_header" -D "$hdr" -o "$body" "$url"
else
curl -sS -L -A "$UA" -m "$DOWNLOAD_TIMEOUT" "${extra[@]}" -D "$hdr" -o "$body" "$url"
fi
rc=$?
set -e
# status = last HTTP status in the chain
status="$(awk '/^HTTP\//{code=$2} END{print code+0}' "$hdr" 2>/dev/null)"
# If curl failed hard, treat as "status undefined"
if [[ $rc -ne 0 || -z "$status" || "$status" -eq 0 ]]; then
echo 0
return 0
fi
echo "$status"
}
# Mimic:
# retries: RETRIES
# delay: DELAY
# until: status is defined and status in [200,403,404]
# failed_when: status is not defined or status not in [200,403,404]
# Returns: "<status> <attempts>" where attempts is how many tries were used.
fetch_index_like_ansible() {
local url="$1" body="$2" hdr="$3"
local attempt status
for ((attempt=1; attempt<=RETRIES; attempt++)); do
status="$(curl_fetch "$url" "$body" "$hdr")"
# until: status defined AND status in allowed
if [[ "$status" -ne 0 ]] && in_list "$status" "${ALLOWED_INDEX_STATUS[@]}"; then
echo "$status $attempt"
return 0
fi
[[ $attempt -lt $RETRIES ]] && sleep "$DELAY"
done
# exhausted retries; status may be 0 or non-allowed
echo "$status $RETRIES"
}
# Mimic your JSON fallback:
# retries: RETRIES
# delay: DELAY
# until: status is defined
# failed_when: false (best-effort)
# Returns: "<status> <attempts>" with status 0 if never got any HTTP status.
fetch_json_best_effort() {
local url="$1" body="$2" hdr="$3"
local attempt status
for ((attempt=1; attempt<=RETRIES; attempt++)); do
status="$(curl_fetch "$url" "$body" "$hdr" 'application/json')"
if [[ "$status" -ne 0 ]]; then
echo "$status $attempt"
return 0
fi
[[ $attempt -lt $RETRIES ]] && sleep "$DELAY"
done
echo "0 $RETRIES"
}
# Extract content.rendered from a WP JSON response (similar to Ansible's parsed json)
# Prints rendered HTML to stdout, or nothing on failure.
extract_wp_rendered() {
local json_file="$1"
if command -v python3 >/dev/null 2>&1; then
python3 - "$json_file" <<'PY'
import json, sys
p = sys.argv[1]
try:
with open(p, 'r', encoding='utf-8', errors='replace') as f:
data = json.load(f)
rendered = data.get('content', {}).get('rendered', '')
if isinstance(rendered, str):
sys.stdout.write(rendered)
except Exception:
pass
PY
else
# Very rough fallback (not JSON-safe); prefer python3.
sed -n 's/.*"rendered"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p' "$json_file" | head -n 1
fi
}
# get_url-like download:
# retries: RETRIES
# delay: DELAY
# until: succeeded
# Ensures final file name is exactly dest (downloads to temp then atomic mv).
# Returns: "<ok(0|1)> <attempts> <http_status>"
download_apk_like_ansible() {
local url="$1" dest="$2"
local attempt status tmp_part hdr
hdr="$tmpdir/apk.hdr"
for ((attempt=1; attempt<=RETRIES; attempt++)); do
tmp_part="${dest}.part.$$"
status="$(curl_fetch "$url" "$tmp_part" "$hdr")"
if [[ "$status" -eq 200 && -s "$tmp_part" ]]; then
mv -f "$tmp_part" "$dest"
echo "1 $attempt $status"
return 0
fi
rm -f "$tmp_part"
[[ $attempt -lt $RETRIES ]] && sleep "$DELAY"
done
echo "0 $RETRIES ${status:-0}"
}
echo "URL: $URL"
echo "Iterations: $N Sleep: ${SLEEP_SEC}s UA: $UA"
echo "RETRIES: $RETRIES DELAY: ${DELAY}s"
[[ -n "$JSON_URL" ]] && echo "JSON_URL: $JSON_URL"
[[ -n "$DOWNLOAD_BASE" ]] && echo "DOWNLOAD_BASE: $DOWNLOAD_BASE"
[[ "$DO_DOWNLOAD" == "1" ]] && echo "DO_DOWNLOAD: 1 (dir: $DOWNLOAD_DIR)"
echo
for ((i=1; i<=N; i++)); do
body="$tmpdir/body.$i.html"
hdr="$tmpdir/hdr.$i.txt"
json_body="$tmpdir/json.$i.json"
json_hdr="$tmpdir/jsonhdr.$i.txt"
rendered_body="$tmpdir/rendered.$i.html"
# 1) Fetch index like Ansible 'uri' task
read -r idx_status idx_attempts < <(fetch_index_like_ansible "$URL" "$body" "$hdr")
idx_retries_used=$((idx_attempts - 1))
# classify status for stats
if [[ "$idx_status" -eq 0 ]]; then
status_key="undefined" # like "status is undefined"
else
status_key="$idx_status"
fi
status_count["$status_key"]=$(( ${status_count["$status_key"]:-0} + 1 ))
retry_hist_index["$idx_retries_used"]=$(( ${retry_hist_index["$idx_retries_used"]:-0} + 1 ))
size="$(wc -c < "$body" | tr -d ' ')"
note_size "$size"
# 2) Detect whether APK links exist
has_apk=0
if grep -Eqo "$APK_RE" "$body"; then
has_apk=1
fi
# 3) Optional JSON fallback if no APK link found
json_status=0
json_attempts=0
if [[ "$has_apk" -eq 0 && -n "$JSON_URL" ]]; then
read -r json_status json_attempts < <(fetch_json_best_effort "$JSON_URL" "$json_body" "$json_hdr")
json_retries_used=$((json_attempts - 1))
retry_hist_json["$json_retries_used"]=$(( ${retry_hist_json["$json_retries_used"]:-0} + 1 ))
# If 200 and we can extract rendered HTML, replace the body (mimic set_fact update)
if [[ "$json_status" -eq 200 ]]; then
extracted="$(extract_wp_rendered "$json_body" || true)"
if [[ -n "$extracted" ]]; then
printf '%s' "$extracted" > "$rendered_body"
body_to_parse="$rendered_body"
else
body_to_parse="$body"
fi
else
body_to_parse="$body"
fi
# re-check APK links after fallback
if grep -Eqo "$APK_RE" "$body_to_parse"; then
has_apk=1
fi
else
body_to_parse="$body"
fi
# 4) Mimic code_blocked_by_cdn condition:
# blocked if 403 OR (apk_missing even after fallback)
blocked=0
if [[ "$idx_status" -eq 403 || "$has_apk" -eq 0 ]]; then
blocked=1
fi
blocked_count["$blocked"]=$(( ${blocked_count["$blocked"]:-0} + 1 ))
# Additional signal (not in Ansible, but useful)
if grep -Eqi "$CF_RE" "$body_to_parse"; then
cf_count["cf_like"]=$(( ${cf_count["cf_like"]:-0} + 1 ))
else
cf_count["no_cf"]=$(( ${cf_count["no_cf"]:-0} + 1 ))
fi
if [[ "$has_apk" -eq 1 ]]; then
apk_count["apk_found"]=$(( ${apk_count["apk_found"]:-0} + 1 ))
else
apk_count["apk_missing"]=$(( ${apk_count["apk_missing"]:-0} + 1 ))
fi
# 5) Optional APK download like Ansible get_url
dl_ok=0
dl_attempts=0
dl_status=0
if [[ "$DO_DOWNLOAD" == "1" && "$blocked" -eq 0 ]]; then
# Match playbook behavior: Jinja2 `sort` is lexicographic.
apk_name="$(grep -Eo "$APK_RE" "$body_to_parse" | LC_ALL=C sort | tail -n 1 || true)"
if [[ -n "$apk_name" ]]; then
# Match Ansible playbook behavior: download comes from a separate base domain.
# If DOWNLOAD_BASE is not set, fall back to the index URL's directory.
if [[ -n "$DOWNLOAD_BASE" ]]; then
apk_url="${DOWNLOAD_BASE%/}/$apk_name"
else
apk_url="${URL%/}/$apk_name"
fi
apk_dest="$DOWNLOAD_DIR/$apk_name"
read -r dl_ok dl_attempts dl_status < <(download_apk_like_ansible "$apk_url" "$apk_dest")
dl_retries_used=$((dl_attempts - 1))
retry_hist_apk["$dl_retries_used"]=$(( ${retry_hist_apk["$dl_retries_used"]:-0} + 1 ))
fi
fi
# progress line
printf "[%03d/%03d] idx=%s(attempts=%s) apk=%s blocked=%s json=%s(attempts=%s)\r" \
"$i" "$N" \
"${idx_status:-0}" "$idx_attempts" \
"$([[ "$has_apk" -eq 1 ]] && echo Y || echo N)" \
"$blocked" \
"${json_status:-0}" "${json_attempts:-0}"
sleep "$SLEEP_SEC"
done
echo -e "\n\n==== SUMMARY (Ansible-like) ===="
echo "Index status counts (0 means status undefined):"
for k in "${!status_count[@]}"; do
printf " %-10s %d\n" "$k" "${status_count[$k]}"
done | sort -k2,2nr
echo
echo "APK armv8a links:"
for k in "${!apk_count[@]}"; do
printf " %-12s %d\n" "$k" "${apk_count[$k]}"
done | sort -k2,2nr
echo
echo "Blocked (Ansible condition: idx==403 OR apk_missing):"
printf " blocked=1 %d\n" "${blocked_count[1]:-0}"
printf " blocked=0 %d\n" "${blocked_count[0]:-0}"
echo
echo "Retry histogram (retries used):"
echo " Index fetch (retries used -> count):"
for k in "${!retry_hist_index[@]}"; do
printf " %s -> %d\n" "$k" "${retry_hist_index[$k]}"
done | sort -n -k1,1
if [[ -n "$JSON_URL" ]]; then
echo " JSON fallback (retries used -> count):"
for k in "${!retry_hist_json[@]}"; do
printf " %s -> %d\n" "$k" "${retry_hist_json[$k]}"
done | sort -n -k1,1
fi
if [[ "$DO_DOWNLOAD" == "1" ]]; then
echo " APK download (retries used -> count):"
for k in "${!retry_hist_apk[@]}"; do
printf " %s -> %d\n" "$k" "${retry_hist_apk[$k]}"
done | sort -n -k1,1
fi
echo
avg_size=$((sum_size / N))
echo "Body size bytes: min=$min_size max=$max_size avg=$avg_size"
echo
if [[ "$DO_DOWNLOAD" == "1" ]]; then
echo "Downloaded APKs are in: $DOWNLOAD_DIR"
fi
echo "Note: files are saved under $tmpdir and will be auto-removed at exit (trap)."