commit 6a7c85dc260d92b90e22c41cd2624411affc69ca Author: sssnake Date: Tue Mar 31 07:18:31 2026 -0700 BuildChain v1.0.0 — on-device Android build toolchain System-level KernelSU module: aapt2/aidl/zipalign (API 35), BusyBox, platform-tools, all as static arm64 binaries in the Android framework. Termux integration for Java/Kotlin/Gradle/Python via pkg. CLI tools: buildchain, bc-build, bc-sign. WebUI on :8089 for toolchain management. Turns any Android phone into a native compilation powerhouse. diff --git a/META-INF/com/google/android/update-binary b/META-INF/com/google/android/update-binary new file mode 100755 index 0000000..f3726fe --- /dev/null +++ b/META-INF/com/google/android/update-binary @@ -0,0 +1,25 @@ +#!/sbin/sh +umask 022 +OUTFD=$2 +ZIPFILE=$3 +ui_print() { echo "$1"; } +mount /data 2>/dev/null +[ -f /data/adb/magisk/util_functions.sh ] || { ui_print "Requires KernelSU-Next"; exit 1; } +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 20400 ] && { ui_print "Requires KernelSU-Next"; exit 1; } +setup_flashable +mount_partitions +api_level_arch_detect +MODID=buildchain +MODPATH=$MOUNTPATH/$MODID +ui_print "- Extracting BuildChain" +unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 +set_perm_recursive $MODPATH 0 0 0755 0644 +set_perm_recursive $MODPATH/tools 0 2000 0755 0755 +set_perm_recursive $MODPATH/scripts 0 2000 0755 0755 +set_perm $MODPATH/service.sh 0 0 0755 +set_perm $MODPATH/post-fs-data.sh 0 0 0755 +set_perm $MODPATH/uninstall.sh 0 0 0755 +ui_print "- BuildChain installed" +ui_print "- Reboot, then run: buildchain setup" +ui_print "- WebUI at http://localhost:8089" diff --git a/META-INF/com/google/android/updater-script b/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/module.prop b/module.prop new file mode 100644 index 0000000..5db013d --- /dev/null +++ b/module.prop @@ -0,0 +1,6 @@ +id=buildchain +name=BuildChain +version=v1.0.0 +versionCode=1 +author=snake +description=On-device Android build toolchain. aapt2/aidl/zipalign (API 35), BusyBox, Python, Java, Kotlin, Gradle via Termux. Turns any phone into a native compiler. WebUI for toolchain management. diff --git a/post-fs-data.sh b/post-fs-data.sh new file mode 100755 index 0000000..72446c4 --- /dev/null +++ b/post-fs-data.sh @@ -0,0 +1,26 @@ +#!/system/bin/sh +# BuildChain — early boot: install busybox applets system-wide, set up paths +MODDIR=${0%/*} +CONFIG_DIR="/data/adb/buildchain" + +mkdir -p "$CONFIG_DIR" +echo "$MODDIR" > "$CONFIG_DIR/moddir" + +# Install busybox applets to /system/xbin via module overlay +XBIN="$MODDIR/system/xbin" +mkdir -p "$XBIN" + +if [ -f "$MODDIR/tools/busybox" ]; then + cp "$MODDIR/tools/busybox" "$XBIN/busybox" + chmod 0755 "$XBIN/busybox" + + # Create symlinks for all busybox applets + "$XBIN/busybox" --list 2>/dev/null | while read applet; do + [ "$applet" = "busybox" ] && continue + # Don't override core Android binaries + case "$applet" in + sh|ls|cat|cp|mv|rm|mkdir|chmod|chown|mount|umount|reboot|ps|kill|ln|df|du) continue ;; + esac + ln -sf busybox "$XBIN/$applet" 2>/dev/null + done +fi diff --git a/scripts/bc-build b/scripts/bc-build new file mode 100755 index 0000000..2f7b98a --- /dev/null +++ b/scripts/bc-build @@ -0,0 +1,168 @@ +#!/system/bin/sh +# bc-build — compile Android project from source on-device +# Supports: single Java/Kotlin file, Gradle project, or raw aapt2+javac+d8 pipeline +# +# Usage: +# bc-build Compile single Java file to APK +# bc-build Compile single Kotlin file to APK +# bc-build Run gradle assembleDebug +# bc-build --raw Manual aapt2+javac+d8 pipeline + +CONFIG_DIR="/data/adb/buildchain" +MODDIR=$(cat "$CONFIG_DIR/moddir" 2>/dev/null) +TOOLS="$MODDIR/tools" +AAPT2="$TOOLS/build-tools/aapt2" +AIDL="$TOOLS/build-tools/aidl" +ZIPALIGN="$TOOLS/build-tools/zipalign" +TBIN="/data/data/com.termux/files/usr/bin" +ANDROID_JAR="/data/data/com.termux/files/home/android-sdk/platforms/android-35/android.jar" + +# Fallback android.jar location +[ ! -f "$ANDROID_JAR" ] && ANDROID_JAR="/system/framework/framework.jar" + +die() { echo "ERROR: $1"; exit 1; } + +build_single_java() { + local src="$1" + local name=$(basename "$src" .java) + local outdir="${2:-./build}" + + echo "=== Building $name.java ===" + mkdir -p "$outdir/classes" "$outdir/dex" "$outdir/apk" + + echo "[1/5] Compiling Java..." + "$TBIN/javac" -source 11 -target 11 -cp "$ANDROID_JAR" -d "$outdir/classes" "$src" || die "javac failed" + + echo "[2/5] Converting to DEX..." + "$TBIN/d8" --min-api 28 --output "$outdir/dex/" "$outdir/classes"/*.class 2>/dev/null \ + || "$TBIN/java" -jar "$TBIN/../share/d8/d8.jar" --min-api 28 --output "$outdir/dex/" "$outdir/classes"/*.class \ + || die "d8/dex conversion failed" + + echo "[3/5] Packaging APK..." + # Create minimal AndroidManifest.xml + cat > "$outdir/AndroidManifest.xml" << MANIFEST + + + + + + + + + + + +MANIFEST + + "$AAPT2" link -o "$outdir/apk/$name.unsigned.apk" \ + --manifest "$outdir/AndroidManifest.xml" \ + -I "$ANDROID_JAR" 2>/dev/null || die "aapt2 link failed" + + # Add DEX to APK + cd "$outdir/dex" && zip -u "../apk/$name.unsigned.apk" classes.dex 2>/dev/null && cd - >/dev/null + + echo "[4/5] Aligning..." + "$ZIPALIGN" -f 4 "$outdir/apk/$name.unsigned.apk" "$outdir/apk/$name.aligned.apk" || die "zipalign failed" + + echo "[5/5] Signing..." + bc-sign "$outdir/apk/$name.aligned.apk" "$outdir/$name.apk" + + echo "" + echo "Output: $outdir/$name.apk" + ls -la "$outdir/$name.apk" 2>/dev/null +} + +build_single_kotlin() { + local src="$1" + local name=$(basename "$src" .kt) + local outdir="${2:-./build}" + + echo "=== Building $name.kt ===" + mkdir -p "$outdir/classes" "$outdir/dex" "$outdir/apk" + + echo "[1/5] Compiling Kotlin..." + "$TBIN/kotlinc" -cp "$ANDROID_JAR" -d "$outdir/classes" "$src" || die "kotlinc failed" + + echo "[2/5] Converting to DEX..." + find "$outdir/classes" -name "*.class" | xargs "$TBIN/d8" --min-api 28 --output "$outdir/dex/" 2>/dev/null \ + || die "d8 failed" + + echo "[3/5] Packaging..." + cat > "$outdir/AndroidManifest.xml" << MANIFEST + + + + +MANIFEST + + "$AAPT2" link -o "$outdir/apk/$name.unsigned.apk" \ + --manifest "$outdir/AndroidManifest.xml" \ + -I "$ANDROID_JAR" 2>/dev/null || die "aapt2 link failed" + + cd "$outdir/dex" && zip -u "../apk/$name.unsigned.apk" classes.dex 2>/dev/null && cd - >/dev/null + + echo "[4/5] Aligning..." + "$ZIPALIGN" -f 4 "$outdir/apk/$name.unsigned.apk" "$outdir/apk/$name.aligned.apk" + + echo "[5/5] Signing..." + bc-sign "$outdir/apk/$name.aligned.apk" "$outdir/$name.apk" + + echo "" + echo "Output: $outdir/$name.apk" +} + +build_gradle_project() { + local projdir="$1" + echo "=== Building Gradle project: $projdir ===" + + if [ ! -f "$projdir/build.gradle" ] && [ ! -f "$projdir/build.gradle.kts" ]; then + die "No build.gradle found in $projdir" + fi + + cd "$projdir" + + # Set ANDROID_HOME for Gradle + export ANDROID_HOME="/data/data/com.termux/files/home/android-sdk" + export ANDROID_SDK_ROOT="$ANDROID_HOME" + + if [ -f "./gradlew" ]; then + chmod +x ./gradlew + ./gradlew assembleDebug || die "Gradle build failed" + elif command -v gradle >/dev/null 2>&1; then + gradle assembleDebug || die "Gradle build failed" + else + die "No gradlew and gradle not installed. Run: buildchain setup" + fi + + echo "" + echo "Output APKs:" + find . -name "*.apk" -path "*/build/outputs/*" 2>/dev/null +} + +# Main +case "$1" in + --help|-h|"") + echo "bc-build — compile Android projects on-device" + echo "" + echo "Usage:" + echo " bc-build MyApp.java Compile Java to APK" + echo " bc-build MyApp.kt Compile Kotlin to APK" + echo " bc-build ./my-project/ Run Gradle assembleDebug" + echo " bc-build MyApp.java ./out/ Specify output directory" + ;; + *.java) + build_single_java "$1" "$2" + ;; + *.kt) + build_single_kotlin "$1" "$2" + ;; + *) + if [ -d "$1" ]; then + build_gradle_project "$1" + else + die "Unknown input: $1 (expected .java, .kt, or directory)" + fi + ;; +esac diff --git a/scripts/bc-sign b/scripts/bc-sign new file mode 100755 index 0000000..fdca441 --- /dev/null +++ b/scripts/bc-sign @@ -0,0 +1,83 @@ +#!/system/bin/sh +# bc-sign — sign APKs with debug or release keystore +# Usage: bc-sign [output.apk] [--release keystore.jks] + +CONFIG_DIR="/data/adb/buildchain" +TBIN="/data/data/com.termux/files/usr/bin" +DEBUG_KS="$CONFIG_DIR/debug.keystore" +DEBUG_PASS="android" +DEBUG_ALIAS="androiddebugkey" + +die() { echo "ERROR: $1"; exit 1; } + +# Create debug keystore if it doesn't exist +ensure_debug_keystore() { + if [ ! -f "$DEBUG_KS" ]; then + echo "Creating debug keystore..." + "$TBIN/keytool" -genkey -v \ + -keystore "$DEBUG_KS" \ + -storepass "$DEBUG_PASS" \ + -alias "$DEBUG_ALIAS" \ + -keypass "$DEBUG_PASS" \ + -keyalg RSA -keysize 2048 -validity 10000 \ + -dname "CN=Android Debug,O=Android,C=US" 2>/dev/null || die "keytool failed — is Java installed? Run: buildchain setup" + fi +} + +INPUT="$1" +OUTPUT="${2:-$(echo "$INPUT" | sed 's/\.unsigned\.apk$/.apk/' | sed 's/\.aligned\.apk$/.apk/')}" + +[ -z "$INPUT" ] && { echo "Usage: bc-sign [output.apk] [--release keystore.jks]"; exit 1; } +[ ! -f "$INPUT" ] && die "File not found: $INPUT" + +# Check for release signing +if [ "$3" = "--release" ] && [ -n "$4" ]; then + KEYSTORE="$4" + [ ! -f "$KEYSTORE" ] && die "Keystore not found: $KEYSTORE" + echo "Signing with release key: $KEYSTORE" + read -p "Keystore password: " KS_PASS + read -p "Key alias: " KS_ALIAS + read -p "Key password: " KEY_PASS + + "$TBIN/apksigner" sign \ + --ks "$KEYSTORE" \ + --ks-pass "pass:$KS_PASS" \ + --ks-key-alias "$KS_ALIAS" \ + --key-pass "pass:$KEY_PASS" \ + --v1-signing-enabled true \ + --v2-signing-enabled true \ + --v3-signing-enabled true \ + --out "$OUTPUT" \ + "$INPUT" || die "apksigner failed" +else + # Debug sign + ensure_debug_keystore + + # Try apksigner first (from SDK), fall back to jarsigner + if command -v apksigner >/dev/null 2>&1 || [ -f "$TBIN/apksigner" ]; then + SIGNER=$(command -v apksigner 2>/dev/null || echo "$TBIN/apksigner") + "$SIGNER" sign \ + --ks "$DEBUG_KS" \ + --ks-pass "pass:$DEBUG_PASS" \ + --ks-key-alias "$DEBUG_ALIAS" \ + --key-pass "pass:$DEBUG_PASS" \ + --out "$OUTPUT" \ + "$INPUT" 2>/dev/null && echo "Signed (apksigner): $OUTPUT" && exit 0 + fi + + # Fallback to jarsigner + if [ -f "$TBIN/jarsigner" ]; then + cp "$INPUT" "$OUTPUT" 2>/dev/null + "$TBIN/jarsigner" \ + -keystore "$DEBUG_KS" \ + -storepass "$DEBUG_PASS" \ + -keypass "$DEBUG_PASS" \ + -signedjar "$OUTPUT" \ + "$INPUT" \ + "$DEBUG_ALIAS" 2>/dev/null && echo "Signed (jarsigner): $OUTPUT" && exit 0 + fi + + die "No signing tool found. Install Java: buildchain setup" +fi + +echo "Signed: $OUTPUT" diff --git a/scripts/buildchain b/scripts/buildchain new file mode 100755 index 0000000..f0c5af8 --- /dev/null +++ b/scripts/buildchain @@ -0,0 +1,164 @@ +#!/system/bin/sh +# BuildChain — main CLI tool +# Usage: buildchain [args] + +CONFIG_DIR="/data/adb/buildchain" +MODDIR=$(cat "$CONFIG_DIR/moddir" 2>/dev/null) +TOOLS="$MODDIR/tools" +VERSION="1.0.0" + +usage() { + echo "BuildChain v$VERSION — On-device Android build toolchain" + echo "" + echo "Usage: buildchain " + echo "" + echo "Commands:" + echo " status Show installed toolchain components" + echo " setup Install Java, Python, Kotlin, Gradle via Termux" + echo " env Print current build environment" + echo " paths Show all tool paths" + echo " test Test all build tools" + echo " version Show version info" + echo "" + echo "Build helpers:" + echo " bc-build Compile an Android project" + echo " bc-sign Sign an APK with debug or release key" + echo "" + echo "WebUI: http://localhost:8089" +} + +cmd_status() { + echo "=== BuildChain Status ===" + echo "" + + echo "[Build Tools — API 35]" + for tool in aapt aapt2 aidl dexdump zipalign split-select; do + local path="$TOOLS/build-tools/$tool" + if [ -f "$path" ]; then + echo " [OK] $tool" + else + echo " [--] $tool (missing)" + fi + done + + echo "" + echo "[Platform Tools]" + for tool in adb fastboot sqlite3 e2fsdroid etc1tool mke2fs make_f2fs sload_f2fs; do + local path="$TOOLS/platform-tools/$tool" + if [ -f "$path" ]; then + echo " [OK] $tool" + else + echo " [--] $tool (missing)" + fi + done + + echo "" + echo "[BusyBox]" + if [ -f "$TOOLS/busybox" ]; then + local applets=$("$TOOLS/busybox" --list 2>/dev/null | wc -l) + echo " [OK] busybox ($applets applets)" + else + echo " [--] busybox (missing)" + fi + + echo "" + echo "[Termux Packages]" + local tbin="/data/data/com.termux/files/usr/bin" + for pkg in java javac python3 kotlin kotlinc gradle git cmake make gcc g++ clang; do + if [ -f "$tbin/$pkg" ]; then + echo " [OK] $pkg" + else + echo " [--] $pkg (run: buildchain setup)" + fi + done + + echo "" + echo "[Android Environment]" + echo " Device: $(getprop ro.product.model)" + echo " Android: $(getprop ro.build.version.release) (API $(getprop ro.build.version.sdk))" + echo " Arch: $(uname -m)" + echo " Kernel: $(uname -r)" +} + +cmd_env() { + [ -f "$CONFIG_DIR/env.sh" ] && cat "$CONFIG_DIR/env.sh" || echo "Environment not configured. Run: buildchain setup" +} + +cmd_paths() { + echo "=== Tool Paths ===" + echo "Build tools: $TOOLS/build-tools/" + echo "Platform tools: $TOOLS/platform-tools/" + echo "BusyBox: $TOOLS/busybox" + echo "Config: $CONFIG_DIR/" + echo "Module: $MODDIR" + echo "" + echo "=== Resolved Binaries ===" + for tool in aapt2 aidl zipalign java javac python3 kotlinc gradle git make cmake clang busybox; do + local p=$(which "$tool" 2>/dev/null) + [ -n "$p" ] && echo " $tool -> $p" || echo " $tool -> (not found)" + done +} + +cmd_test() { + echo "=== Testing Build Tools ===" + echo "" + + echo "aapt2:" + "$TOOLS/build-tools/aapt2" version 2>&1 && echo " PASS" || echo " FAIL" + echo "" + + echo "aidl:" + "$TOOLS/build-tools/aidl" --version 2>&1 && echo " PASS" || echo " FAIL" + echo "" + + echo "zipalign:" + "$TOOLS/build-tools/zipalign" 2>&1 | head -1 && echo " PASS" || echo " FAIL" + echo "" + + echo "dexdump:" + "$TOOLS/build-tools/dexdump" 2>&1 | head -1 && echo " PASS" || echo " FAIL" + echo "" + + echo "busybox:" + "$TOOLS/busybox" --help 2>&1 | head -1 && echo " PASS" || echo " FAIL" + echo "" + + # Test Termux tools if available + local tbin="/data/data/com.termux/files/usr/bin" + if [ -f "$tbin/java" ]; then + echo "java:" + "$tbin/java" -version 2>&1 | head -1 + echo "" + fi + if [ -f "$tbin/python3" ]; then + echo "python:" + "$tbin/python3" --version 2>&1 + echo "" + fi + if [ -f "$tbin/kotlinc" ]; then + echo "kotlin:" + "$tbin/kotlinc" -version 2>&1 | head -1 + echo "" + fi + if [ -f "$tbin/gradle" ]; then + echo "gradle:" + "$tbin/gradle" --version 2>&1 | grep "Gradle " | head -1 + echo "" + fi +} + +cmd_version() { + echo "BuildChain v$VERSION" + echo "Build tools: API 35 (35.0.2)" + echo "Target: $(getprop ro.product.model) / Android $(getprop ro.build.version.release)" +} + +case "$1" in + status) cmd_status ;; + setup) sh "$MODDIR/scripts/buildchain-setup" ;; + env) cmd_env ;; + paths) cmd_paths ;; + test) cmd_test ;; + version) cmd_version ;; + *) usage ;; +esac diff --git a/scripts/buildchain-setup b/scripts/buildchain-setup new file mode 100755 index 0000000..7dff9e8 --- /dev/null +++ b/scripts/buildchain-setup @@ -0,0 +1,91 @@ +#!/system/bin/sh +# BuildChain Setup — installs Java, Python, Kotlin, Gradle, and dev tools via Termux +# Must be run from within Termux (or any shell with access to pkg/apt) + +TERMUX_PREFIX="/data/data/com.termux/files/usr" +TERMUX_BIN="$TERMUX_PREFIX/bin" + +echo "=== BuildChain Setup ===" +echo "" + +# Check if we're in a Termux-capable environment +if [ ! -f "$TERMUX_BIN/pkg" ] && ! command -v pkg >/dev/null 2>&1; then + echo "ERROR: Termux pkg not found." + echo "This script must be run inside Termux or after Termux is installed." + echo "" + echo "Install Termux from F-Droid or GitHub, open it once, then run this again." + exit 1 +fi + +PKG="pkg" +command -v pkg >/dev/null 2>&1 || PKG="$TERMUX_BIN/pkg" + +echo "[1/7] Updating package database..." +$PKG update -y 2>&1 | tail -3 + +echo "" +echo "[2/7] Installing Java (OpenJDK 21)..." +$PKG install -y openjdk-21 2>&1 | tail -3 +echo " $(java -version 2>&1 | head -1)" + +echo "" +echo "[3/7] Installing Python..." +$PKG install -y python 2>&1 | tail -3 +echo " $(python3 --version 2>&1)" + +echo "" +echo "[4/7] Installing Kotlin..." +$PKG install -y kotlin 2>&1 | tail -3 +echo " $(kotlinc -version 2>&1 | head -1)" + +echo "" +echo "[5/7] Installing Gradle..." +$PKG install -y gradle 2>&1 | tail -3 +echo " $(gradle --version 2>&1 | grep 'Gradle ' | head -1)" + +echo "" +echo "[6/7] Installing build essentials (git, cmake, make, clang, ndk tools)..." +$PKG install -y git cmake make clang ndk-sysroot binutils 2>&1 | tail -3 + +echo "" +echo "[7/7] Installing extra tools (curl, wget, zip, unzip, tar, openssh, jq)..." +$PKG install -y curl wget zip unzip tar openssh jq 2>&1 | tail -3 + +echo "" +echo "=== Setting up Android SDK structure ===" +ANDROID_HOME="/data/data/com.termux/files/home/android-sdk" +mkdir -p "$ANDROID_HOME/build-tools/35.0.2" +mkdir -p "$ANDROID_HOME/platform-tools" + +# Symlink our build tools into the SDK structure so Gradle/AGP finds them +MODDIR=$(cat /data/adb/buildchain/moddir 2>/dev/null) +if [ -n "$MODDIR" ]; then + for tool in aapt aapt2 aidl dexdump zipalign split-select; do + ln -sf "$MODDIR/tools/build-tools/$tool" "$ANDROID_HOME/build-tools/35.0.2/$tool" 2>/dev/null + done + for tool in adb fastboot sqlite3; do + ln -sf "$MODDIR/tools/platform-tools/$tool" "$ANDROID_HOME/platform-tools/$tool" 2>/dev/null + done + echo " SDK structure created at $ANDROID_HOME" +fi + +echo "" +echo "=== Setup Complete ===" +echo "" +echo "Installed:" +echo " Java: $(java -version 2>&1 | head -1)" +echo " Python: $(python3 --version 2>&1)" +echo " Kotlin: $(kotlinc -version 2>&1 | head -1)" +echo " Gradle: $(gradle --version 2>&1 | grep 'Gradle ' | head -1)" +echo " Git: $(git --version 2>&1)" +echo " CMake: $(cmake --version 2>&1 | head -1)" +echo " Clang: $(clang --version 2>&1 | head -1)" +echo "" +echo "Build tools: aapt2, aidl, zipalign (API 35)" +echo "BusyBox: $(busybox 2>&1 | head -1)" +echo "" +echo "Run 'buildchain status' to verify everything." +echo "Run 'buildchain test' to test all tools." +echo "" +echo "Source the environment in your shell:" +echo " source /data/adb/buildchain/env.sh" diff --git a/scripts/webui-server b/scripts/webui-server new file mode 100755 index 0000000..0ef82ee --- /dev/null +++ b/scripts/webui-server @@ -0,0 +1,157 @@ +#!/system/bin/sh +# BuildChain WebUI — HTTP server on port 8089 +MODDIR=$(cat /data/adb/buildchain/moddir 2>/dev/null) +WEBROOT="$MODDIR/webroot" +CONFIG_DIR="/data/adb/buildchain" +TOOLS="$MODDIR/tools" +PORT=8089 +TERMUX_BIN="/data/data/com.termux/files/usr/bin" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; } + +json_val() { echo "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"; } + +get_mime() { + case "$1" in *.html) echo "text/html";; *.css) echo "text/css";; *.js) echo "text/javascript";; *) echo "application/json";; esac +} + +send_response() { + printf "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET,POST,OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n\r\n%s" \ + "$1" "$2" "${#3}" "$3" +} + +send_file() { + [ -f "$1" ] && send_response "200 OK" "$(get_mime "$1")" "$(cat "$1")" || send_response "404 Not Found" "text/plain" "Not Found" +} + +# API: get full environment status +api_status() { + cat "$CONFIG_DIR/environment.json" 2>/dev/null || echo '{"error":"not detected yet"}' +} + +# API: list all tools and their paths/status +api_tools() { + local result="[" + local first=1 + + for tool in aapt aapt2 aidl dexdump zipalign split-select; do + local path="$TOOLS/build-tools/$tool" + local exists="false"; [ -f "$path" ] && exists="true" + local size=$(stat -c%s "$path" 2>/dev/null || echo 0) + [ "$first" = "1" ] && first=0 || result="$result," + result="$result{\"name\":\"$tool\",\"category\":\"build-tools\",\"path\":\"$path\",\"exists\":$exists,\"size\":$size}" + done + + for tool in adb fastboot sqlite3 e2fsdroid etc1tool mke2fs make_f2fs sload_f2fs hprof-conv; do + local path="$TOOLS/platform-tools/$tool" + local exists="false"; [ -f "$path" ] && exists="true" + local size=$(stat -c%s "$path" 2>/dev/null || echo 0) + result="$result,{\"name\":\"$tool\",\"category\":\"platform-tools\",\"path\":\"$path\",\"exists\":$exists,\"size\":$size}" + done + + result="$result,{\"name\":\"busybox\",\"category\":\"system\",\"path\":\"$TOOLS/busybox\",\"exists\":$([ -f "$TOOLS/busybox" ] && echo true || echo false),\"size\":$(stat -c%s "$TOOLS/busybox" 2>/dev/null || echo 0)}" + + # Termux packages + for tool in java javac python3 kotlin kotlinc gradle git cmake make clang gcc g++ d8 apksigner jarsigner keytool; do + local tpath="$TERMUX_BIN/$tool" + local exists="false"; [ -f "$tpath" ] && exists="true" + result="$result,{\"name\":\"$tool\",\"category\":\"termux\",\"path\":\"$tpath\",\"exists\":$exists,\"size\":0}" + done + + echo "${result}]" +} + +# API: get current PATH +api_paths() { + local syspath=$(echo "$PATH" | tr ':' '\n') + local result="[" + local first=1 + for p in $syspath; do + [ "$first" = "1" ] && first=0 || result="$result," + result="$result\"$p\"" + done + echo "${result}]" +} + +# API: run buildchain test +api_test() { + local result="" + result=$(sh "$MODDIR/scripts/buildchain" test 2>&1) + result=$(echo "$result" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g') + echo "{\"output\":\"$result\"}" +} + +# API: run setup (returns instructions since it needs Termux context) +api_setup_status() { + local missing="" + for pkg in java python3 kotlinc gradle git cmake clang; do + [ ! -f "$TERMUX_BIN/$pkg" ] && missing="$missing $pkg" + done + if [ -z "$missing" ]; then + echo '{"complete":true,"missing":[]}' + else + missing=$(echo "$missing" | sed 's/^ //' | sed 's/ /","/g') + echo "{\"complete\":false,\"missing\":[\"$missing\"]}" + fi +} + +# API: get buildchain log +api_log() { + local lines="${1:-50}" + local content=$(tail -n "$lines" "$CONFIG_DIR/buildchain.log" 2>/dev/null) + content=$(echo "$content" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g') + echo "{\"log\":\"$content\"}" +} + +# API: busybox applet list +api_busybox() { + local applets=$("$TOOLS/busybox" --list 2>/dev/null | tr '\n' ',' | sed 's/,$//') + local count=$("$TOOLS/busybox" --list 2>/dev/null | wc -l) + local version=$("$TOOLS/busybox" 2>&1 | head -1 | sed 's/"/\\"/g') + echo "{\"version\":\"$version\",\"count\":$count,\"applets\":\"$applets\"}" +} + +handle_request() { + local method path body content_length + + read -r request_line + method=$(echo "$request_line" | awk '{print $1}') + path=$(echo "$request_line" | awk '{print $2}' | cut -d'?' -f1) + + while read -r header; do + header=$(echo "$header" | tr -d '\r') + [ -z "$header" ] && break + case "$header" in Content-Length:*) content_length=$(echo "$header" | awk '{print $2}') ;; esac + done + + body="" + if [ "$method" = "POST" ] && [ -n "$content_length" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then + body=$(dd bs=1 count="$content_length" 2>/dev/null) + fi + + [ "$method" = "OPTIONS" ] && { send_response "200 OK" "text/plain" ""; return; } + + case "$method $path" in + "GET /") send_file "$WEBROOT/index.html" ;; + "GET /css/"*|"GET /js/"*) send_file "$WEBROOT$path" ;; + "GET /api/status") send_response "200 OK" "application/json" "$(api_status)" ;; + "GET /api/tools") send_response "200 OK" "application/json" "$(api_tools)" ;; + "GET /api/paths") send_response "200 OK" "application/json" "$(api_paths)" ;; + "GET /api/test") send_response "200 OK" "application/json" "$(api_test)" ;; + "GET /api/setup") send_response "200 OK" "application/json" "$(api_setup_status)" ;; + "GET /api/log") send_response "200 OK" "application/json" "$(api_log)" ;; + "GET /api/busybox") send_response "200 OK" "application/json" "$(api_busybox)" ;; + "POST /api/redetect") + sh "$MODDIR/service.sh" & + send_response "200 OK" "application/json" '{"ok":true}' + ;; + *) send_response "404 Not Found" "application/json" '{"error":"not found"}' ;; + esac +} + +log "Starting on port $PORT" +while true; do + handle_request | busybox nc -ll -p "$PORT" -s 127.0.0.1 2>/dev/null \ + || handle_request | toybox nc -L -p "$PORT" -s 127.0.0.1 2>/dev/null \ + || { log "ERROR: No listener"; exit 1; } +done diff --git a/service.sh b/service.sh new file mode 100755 index 0000000..54744d4 --- /dev/null +++ b/service.sh @@ -0,0 +1,195 @@ +#!/system/bin/sh +# BuildChain — late service: link tools into framework, hook Termux, start WebUI +MODDIR=${0%/*} +CONFIG_DIR="/data/adb/buildchain" +LOG="$CONFIG_DIR/buildchain.log" +PID_FILE="$CONFIG_DIR/webui.pid" +TOOLS="$MODDIR/tools" +TERMUX_PREFIX="/data/data/com.termux/files/usr" +TERMUX_BIN="$TERMUX_PREFIX/bin" +TERMUX_LIB="$TERMUX_PREFIX/lib" +TERMUX_ETC="$TERMUX_PREFIX/etc" +TERMUX_HOME="/data/data/com.termux/files/home" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG"; } + +mkdir -p "$CONFIG_DIR" +log "BuildChain service starting" + +# Wait for system to settle +sleep 8 + +############################# +# Detect installed components +############################# + +detect_environment() { + local env_file="$CONFIG_DIR/environment.json" + + local has_termux="false" + local has_termux_api="false" + local has_java="false" + local java_version="" + local has_python="false" + local python_version="" + local has_kotlin="false" + local kotlin_version="" + local has_gradle="false" + local gradle_version="" + local has_git="false" + + [ -d "$TERMUX_BIN" ] && has_termux="true" + + # Check Termux packages + if [ "$has_termux" = "true" ]; then + [ -f "$TERMUX_BIN/termux-api-start" ] || pm list packages 2>/dev/null | grep -q "com.termux.api" && has_termux_api="true" + if [ -f "$TERMUX_BIN/java" ]; then + has_java="true" + java_version=$("$TERMUX_BIN/java" -version 2>&1 | head -1) + fi + if [ -f "$TERMUX_BIN/python" ] || [ -f "$TERMUX_BIN/python3" ]; then + has_python="true" + python_version=$("$TERMUX_BIN/python3" --version 2>/dev/null || "$TERMUX_BIN/python" --version 2>/dev/null) + fi + if [ -f "$TERMUX_BIN/kotlin" ] || [ -f "$TERMUX_BIN/kotlinc" ]; then + has_kotlin="true" + kotlin_version=$("$TERMUX_BIN/kotlinc" -version 2>&1 | head -1) + fi + if [ -f "$TERMUX_BIN/gradle" ]; then + has_gradle="true" + gradle_version=$("$TERMUX_BIN/gradle" --version 2>/dev/null | grep "Gradle " | head -1) + fi + [ -f "$TERMUX_BIN/git" ] && has_git="true" + fi + + # Arch + local arch=$(uname -m) + local api=$(getprop ro.build.version.sdk) + local device=$(getprop ro.product.model) + + cat > "$env_file" << ENVJSON +{ + "arch": "$arch", + "api_level": "$api", + "device": "$device", + "termux": $has_termux, + "termux_api": $has_termux_api, + "java": $has_java, + "java_version": "$java_version", + "python": $has_python, + "python_version": "$python_version", + "kotlin": $has_kotlin, + "kotlin_version": "$kotlin_version", + "gradle": $has_gradle, + "gradle_version": "$gradle_version", + "git": $has_git, + "build_tools_version": "35.0.2", + "busybox": true +} +ENVJSON + + log "Environment detected: termux=$has_termux java=$has_java python=$has_python kotlin=$has_kotlin gradle=$has_gradle" +} + +detect_environment + +############################# +# Link build tools into system PATH (via /system/xbin overlay) +############################# + +link_system_tools() { + local xbin="$MODDIR/system/xbin" + mkdir -p "$xbin" + + # Build tools — always available system-wide + for tool in aapt aapt2 aidl dexdump zipalign split-select; do + [ -f "$TOOLS/build-tools/$tool" ] && ln -sf "$TOOLS/build-tools/$tool" "$xbin/$tool" 2>/dev/null + done + + # Platform tools that aren't already in /system/bin + for tool in sqlite3 etc1tool hprof-conv e2fsdroid make_f2fs sload_f2fs mke2fs; do + [ -f "$TOOLS/platform-tools/$tool" ] && ln -sf "$TOOLS/platform-tools/$tool" "$xbin/$tool" 2>/dev/null + done + + # Our management scripts + for script in "$MODDIR"/scripts/*; do + [ -f "$script" ] && ln -sf "$script" "$xbin/$(basename $script)" 2>/dev/null + done + + log "System tools linked to $xbin" +} + +link_system_tools + +############################# +# Hook into Termux +############################# + +hook_termux() { + if [ ! -d "$TERMUX_BIN" ]; then + log "Termux not installed — skipping hooks" + return + fi + + # Symlink build tools into Termux bin (so they're on Termux's PATH too) + for tool in aapt aapt2 aidl dexdump zipalign split-select; do + [ -f "$TOOLS/build-tools/$tool" ] && ln -sf "$TOOLS/build-tools/$tool" "$TERMUX_BIN/$tool" 2>/dev/null + done + + # Link our CLI scripts into Termux + for script in "$MODDIR"/scripts/*; do + [ -f "$script" ] && ln -sf "$script" "$TERMUX_BIN/$(basename $script)" 2>/dev/null + done + + # Create Termux profile.d hook for environment + mkdir -p "$TERMUX_ETC/profile.d" + cat > "$TERMUX_ETC/profile.d/buildchain.sh" << 'PROFEOF' +# BuildChain — auto-sourced by Termux on shell start +[ -f /data/adb/buildchain/env.sh ] && . /data/adb/buildchain/env.sh +PROFEOF + + # Write the environment file + cat > "$CONFIG_DIR/env.sh" << ENVEOF +# BuildChain environment +export BUILDCHAIN_HOME="$MODDIR" +export ANDROID_BUILD_TOOLS="$TOOLS/build-tools" +export ANDROID_PLATFORM_TOOLS="$TOOLS/platform-tools" +export ANDROID_HOME="$TERMUX_HOME/android-sdk" +export ANDROID_SDK_ROOT="\$ANDROID_HOME" +export PATH="\$ANDROID_BUILD_TOOLS:\$ANDROID_PLATFORM_TOOLS:\$PATH" + +# Java — detect from Termux +for jdir in $TERMUX_PREFIX/lib/jvm/java-21-openjdk $TERMUX_PREFIX/lib/jvm/java-17-openjdk; do + if [ -d "\$jdir" ]; then + export JAVA_HOME="\$jdir" + export PATH="\$JAVA_HOME/bin:\$PATH" + break + fi +done + +# Gradle home +[ -d "$TERMUX_HOME/.gradle" ] && export GRADLE_USER_HOME="$TERMUX_HOME/.gradle" + +# Kotlin +[ -d "$TERMUX_PREFIX/share/kotlin" ] && export KOTLIN_HOME="$TERMUX_PREFIX/share/kotlin" +ENVEOF + + log "Termux hooks installed" +} + +hook_termux + +############################# +# Start WebUI +############################# + +if [ -f "$PID_FILE" ]; then + kill $(cat "$PID_FILE") 2>/dev/null + rm -f "$PID_FILE" +fi + +log "Starting WebUI on port 8089" +nohup sh "$MODDIR/scripts/webui-server" >> "$LOG" 2>&1 & +echo $! > "$PID_FILE" + +log "BuildChain service complete (WebUI PID: $(cat $PID_FILE))" diff --git a/tools/build-tools/aapt b/tools/build-tools/aapt new file mode 100755 index 0000000..724a9e7 Binary files /dev/null and b/tools/build-tools/aapt differ diff --git a/tools/build-tools/aapt2 b/tools/build-tools/aapt2 new file mode 100755 index 0000000..09fa323 Binary files /dev/null and b/tools/build-tools/aapt2 differ diff --git a/tools/build-tools/aidl b/tools/build-tools/aidl new file mode 100755 index 0000000..11c7155 Binary files /dev/null and b/tools/build-tools/aidl differ diff --git a/tools/build-tools/dexdump b/tools/build-tools/dexdump new file mode 100755 index 0000000..a7dee76 Binary files /dev/null and b/tools/build-tools/dexdump differ diff --git a/tools/build-tools/split-select b/tools/build-tools/split-select new file mode 100755 index 0000000..b3f7593 Binary files /dev/null and b/tools/build-tools/split-select differ diff --git a/tools/build-tools/zipalign b/tools/build-tools/zipalign new file mode 100755 index 0000000..ff47e8e Binary files /dev/null and b/tools/build-tools/zipalign differ diff --git a/tools/busybox b/tools/busybox new file mode 100755 index 0000000..7081454 Binary files /dev/null and b/tools/busybox differ diff --git a/tools/platform-tools/adb b/tools/platform-tools/adb new file mode 100755 index 0000000..59209b0 Binary files /dev/null and b/tools/platform-tools/adb differ diff --git a/tools/platform-tools/e2fsdroid b/tools/platform-tools/e2fsdroid new file mode 100755 index 0000000..8285f63 Binary files /dev/null and b/tools/platform-tools/e2fsdroid differ diff --git a/tools/platform-tools/etc1tool b/tools/platform-tools/etc1tool new file mode 100755 index 0000000..6faca52 Binary files /dev/null and b/tools/platform-tools/etc1tool differ diff --git a/tools/platform-tools/fastboot b/tools/platform-tools/fastboot new file mode 100755 index 0000000..7d38da7 Binary files /dev/null and b/tools/platform-tools/fastboot differ diff --git a/tools/platform-tools/hprof-conv b/tools/platform-tools/hprof-conv new file mode 100755 index 0000000..b9c155b Binary files /dev/null and b/tools/platform-tools/hprof-conv differ diff --git a/tools/platform-tools/make_f2fs b/tools/platform-tools/make_f2fs new file mode 100755 index 0000000..9bdbb13 Binary files /dev/null and b/tools/platform-tools/make_f2fs differ diff --git a/tools/platform-tools/make_f2fs_casefold b/tools/platform-tools/make_f2fs_casefold new file mode 100755 index 0000000..b8847ed Binary files /dev/null and b/tools/platform-tools/make_f2fs_casefold differ diff --git a/tools/platform-tools/mke2fs b/tools/platform-tools/mke2fs new file mode 100755 index 0000000..40ef30a Binary files /dev/null and b/tools/platform-tools/mke2fs differ diff --git a/tools/platform-tools/sload_f2fs b/tools/platform-tools/sload_f2fs new file mode 100755 index 0000000..7f21381 Binary files /dev/null and b/tools/platform-tools/sload_f2fs differ diff --git a/tools/platform-tools/sqlite3 b/tools/platform-tools/sqlite3 new file mode 100755 index 0000000..0738b39 Binary files /dev/null and b/tools/platform-tools/sqlite3 differ diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..0a171f2 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,15 @@ +#!/system/bin/sh +CONFIG_DIR="/data/adb/buildchain" +PID_FILE="$CONFIG_DIR/webui.pid" +TERMUX_BIN="/data/data/com.termux/files/usr/bin" +TERMUX_ETC="/data/data/com.termux/files/usr/etc" + +[ -f "$PID_FILE" ] && kill $(cat "$PID_FILE") 2>/dev/null + +# Remove Termux symlinks +for tool in aapt aapt2 aidl dexdump zipalign split-select buildchain buildchain-setup bc-build bc-sign; do + rm -f "$TERMUX_BIN/$tool" 2>/dev/null +done +rm -f "$TERMUX_ETC/profile.d/buildchain.sh" 2>/dev/null + +rm -rf "$CONFIG_DIR" diff --git a/webroot/css/style.css b/webroot/css/style.css new file mode 100644 index 0000000..3b91dd9 --- /dev/null +++ b/webroot/css/style.css @@ -0,0 +1,51 @@ +:root { + --bg: #0a0e17; --card: #111827; --card-hover: #1a2332; --input: #1e293b; + --border: #1e3a5f; --text: #e2e8f0; --text2: #94a3b8; --muted: #64748b; + --accent: #10b981; --accent-glow: rgba(16,185,129,0.3); + --success: #22c55e; --warn: #f59e0b; --danger: #ef4444; + --radius: 12px; --sm: 8px; +} +* { margin:0; padding:0; box-sizing:border-box; } +body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; } +body::before { content:''; position:fixed; inset:0; background:radial-gradient(ellipse at 30% 50%,rgba(16,185,129,0.06) 0%,transparent 50%),radial-gradient(ellipse at 70% 20%,rgba(59,130,246,0.05) 0%,transparent 50%); pointer-events:none; z-index:0; } +.app { position:relative; z-index:1; max-width:540px; margin:0 auto; padding:16px; padding-bottom:80px; } +.header { text-align:center; padding:20px 0 16px; } +.header h1 { font-size:22px; font-weight:700; color:var(--accent); } +.header p { font-size:13px; color:var(--muted); margin-top:4px; } +.section { margin-bottom:16px; } +.section-title { font-size:11px; font-weight:600; text-transform:uppercase; letter-spacing:1.2px; color:var(--muted); padding:0 4px; margin-bottom:8px; } +.card { background:var(--card); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; margin-bottom:8px; } +.card-row { display:flex; align-items:center; justify-content:space-between; padding:12px 16px; } +.card-row+.card-row { border-top:1px solid var(--border); } +.card-row-info { flex:1; min-width:0; } +.card-row-label { font-size:14px; font-weight:500; } +.card-row-desc { font-size:12px; color:var(--muted); margin-top:2px; word-break:break-all; } +.prop-name { font-family:'SF Mono','Fira Code',monospace; font-size:12px; color:var(--accent); } +.badge { display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px; font-weight:600; } +.badge-ok { background:rgba(34,197,94,0.15); color:var(--success); } +.badge-miss { background:rgba(239,68,68,0.15); color:var(--danger); } +.badge-info { background:rgba(59,130,246,0.15); color:#3b82f6; } +.tool-grid { display:grid; grid-template-columns:1fr 1fr; gap:6px; padding:12px 16px; } +.tool-chip { padding:8px 10px; border-radius:var(--sm); border:1px solid var(--border); background:var(--input); font-size:12px; display:flex; align-items:center; gap:6px; } +.tool-chip .dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; } +.tool-chip .dot.ok { background:var(--success); box-shadow:0 0 6px var(--success); } +.tool-chip .dot.miss { background:var(--muted); } +.btn { padding:10px 16px; border-radius:var(--sm); border:1px solid var(--border); background:var(--card); color:var(--text); font-size:13px; font-weight:500; cursor:pointer; text-align:center; } +.btn:hover { border-color:var(--accent); } +.btn-primary { background:var(--accent); border-color:var(--accent); color:#000; font-weight:600; } +.btn-primary:hover { box-shadow:0 4px 16px var(--accent-glow); } +.actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:12px; } +pre.output { background:#030712; border:1px solid var(--border); border-radius:var(--sm); padding:12px; font-family:'SF Mono','Fira Code',monospace; font-size:11px; line-height:1.5; color:var(--text2); white-space:pre-wrap; word-break:break-all; max-height:400px; overflow-y:auto; } +.toast { position:fixed; bottom:20px; left:50%; transform:translateX(-50%) translateY(80px); padding:12px 20px; border-radius:var(--sm); background:var(--card); border:1px solid var(--border); color:var(--text); font-size:13px; box-shadow:0 8px 32px rgba(0,0,0,0.4); opacity:0; transition:all 0.3s; z-index:100; } +.toast.show { transform:translateX(-50%) translateY(0); opacity:1; } +.toast.success { border-color:var(--success); } +.toast.error { border-color:var(--danger); } +.tab-bar { position:fixed; bottom:0; left:0; right:0; background:rgba(10,14,23,0.95); backdrop-filter:blur(20px); border-top:1px solid var(--border); display:flex; justify-content:center; gap:4px; padding:8px 16px; padding-bottom:max(8px,env(safe-area-inset-bottom)); z-index:50; } +.tab-item { flex:1; max-width:90px; display:flex; flex-direction:column; align-items:center; gap:3px; padding:6px 4px; border-radius:var(--sm); color:var(--muted); font-size:10px; font-weight:500; cursor:pointer; border:none; background:none; } +.tab-item.active { color:var(--accent); } +.tab-item svg { width:20px; height:20px; fill:currentColor; } +.page { display:none; } .page.active { display:block; } +.info-grid { display:grid; grid-template-columns:1fr 1fr; gap:1px; background:var(--border); border:1px solid var(--border); border-radius:var(--radius); } +.info-cell { background:var(--card); padding:10px 14px; } +.info-label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.5px; } +.info-value { font-size:13px; font-weight:600; margin-top:3px; } diff --git a/webroot/index.html b/webroot/index.html new file mode 100644 index 0000000..28041ac --- /dev/null +++ b/webroot/index.html @@ -0,0 +1,136 @@ + + + + + + + BuildChain + + + +
+
+

BuildChain

+

Detecting...

+
+ + +
+
+
Environment
+
+
Device
-
+
Android / API
-
+
Architecture
-
+
Build Tools
35.0.2
+
+
+ +
+
Toolchain Status
+
+
+
+
+ +
+
Setup
+
+
+
+
Termux Packages
+
Checking...
+
+ ... +
+
+
+
Run in Termux: buildchain setup
+
+
+
+
+ +
+ + +
+
+ + +
+
+
Build Tools (API 35 — Static ARM64)
+
+
+
+
Platform Tools
+
+
+
+
Termux Packages
+
+
+
+
BusyBox
+
+
+
+ + +
+
+
Tool Test Results
+
+ +
Tap "Run All Tests" to verify the toolchain.
+
+
+
+
Service Log
+
+ +
Tap to load.
+
+
+
+ + +
+
+
System PATH
+
+
+
+
Environment Variables
+
+
Loading...
+
+
+
+
+ +
+ +
+ + + + +
+ + + + diff --git a/webroot/js/app.js b/webroot/js/app.js new file mode 100644 index 0000000..c5dea0b --- /dev/null +++ b/webroot/js/app.js @@ -0,0 +1,194 @@ +// BuildChain WebUI + +let currentTab = 'dash'; +let toastTimer = null; + +async function api(path) { + try { return await (await fetch(path)).json(); } catch(e) { showToast('Connection error','error'); return null; } +} +async function post(path, body) { + try { return await (await fetch(path, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)})).json(); } catch(e) { return null; } +} + +function showToast(msg, type='success') { + const el = document.getElementById('toast'); + el.textContent = msg; el.className = 'toast ' + type + ' show'; + clearTimeout(toastTimer); toastTimer = setTimeout(() => el.className = 'toast', 2500); +} + +function switchTab(tab) { + currentTab = tab; + document.querySelectorAll('.page').forEach(p => p.classList.remove('active')); + document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active')); + document.getElementById('page-' + tab).classList.add('active'); + document.querySelector(`[data-tab="${tab}"]`).classList.add('active'); + switch(tab) { + case 'dash': loadDash(); break; + case 'tools': loadTools(); break; + case 'test': break; + case 'paths': loadPaths(); break; + } +} + +function esc(s) { return (s||'').replace(/&/g,'&').replace(//g,'>'); } + +// ---- Dashboard ---- +async function loadDash() { + const env = await api('/api/status'); + if (env) { + document.getElementById('device-info').textContent = `${env.device || '?'} — Android ${env.api_level || '?'} — ${env.arch || '?'}`; + document.getElementById('env-device').textContent = env.device || '-'; + document.getElementById('env-api').textContent = `API ${env.api_level || '?'}`; + document.getElementById('env-arch').textContent = env.arch || '-'; + } + + // Tool status grid + const tools = await api('/api/tools'); + const grid = document.getElementById('status-grid'); + if (tools) { + grid.innerHTML = tools.map(t => ` +
+ + ${t.name} +
+ `).join(''); + } + + // Setup status + const setup = await api('/api/setup'); + const statusEl = document.getElementById('setup-status'); + const badgeEl = document.getElementById('setup-badge'); + if (setup) { + if (setup.complete) { + statusEl.textContent = 'All packages installed'; + badgeEl.textContent = 'Ready'; + badgeEl.className = 'badge badge-ok'; + } else { + statusEl.textContent = 'Missing: ' + setup.missing.join(', '); + badgeEl.textContent = 'Incomplete'; + badgeEl.className = 'badge badge-miss'; + } + } +} + +async function redetect() { + showToast('Re-detecting environment...'); + await post('/api/redetect', {}); + setTimeout(loadDash, 3000); +} + +// ---- Tools ---- +async function loadTools() { + const tools = await api('/api/tools'); + if (!tools) return; + + const buildTools = tools.filter(t => t.category === 'build-tools'); + const platTools = tools.filter(t => t.category === 'platform-tools'); + const termuxTools = tools.filter(t => t.category === 'termux'); + + document.getElementById('build-tools-list').innerHTML = buildTools.map(t => ` +
+
+
${t.name}
+
${t.path}
+
+ ${t.exists ? (t.size/1024).toFixed(0)+'K' : 'missing'} +
+ `).join(''); + + document.getElementById('platform-tools-list').innerHTML = platTools.map(t => ` +
+
+
${t.name}
+
${t.path}
+
+ ${t.exists ? (t.size/1024).toFixed(0)+'K' : 'missing'} +
+ `).join(''); + + document.getElementById('termux-tools-list').innerHTML = termuxTools.map(t => ` +
+
+
${t.name}
+
${t.category}
+
+ ${t.exists ? 'installed' : 'not found'} +
+ `).join(''); + + // BusyBox + const bb = await api('/api/busybox'); + const bbc = document.getElementById('busybox-info'); + if (bb) { + bbc.innerHTML = ` +
+
+
${esc(bb.version)}
+
${bb.count} applets
+
+ active +
+
+
+
${esc(bb.applets).replace(/,/g, ', ')}
+
+
+ `; + } +} + +// ---- Test ---- +async function runTest() { + const out = document.getElementById('test-output'); + out.textContent = 'Running tests...'; + const res = await api('/api/test'); + if (res && res.output) { + out.textContent = res.output; + } else { + out.textContent = 'Failed to run tests'; + } +} + +async function loadLog() { + const out = document.getElementById('log-output'); + out.textContent = 'Loading...'; + const res = await api('/api/log'); + if (res && res.log) { + out.textContent = res.log; + } else { + out.textContent = 'No log available'; + } +} + +// ---- Paths ---- +async function loadPaths() { + const paths = await api('/api/paths'); + const pc = document.getElementById('path-list'); + if (paths) { + pc.innerHTML = paths.map((p, i) => ` +
+
+ ${esc(p)} +
+ #${i} +
+ `).join(''); + } + + // Env file + const envOut = document.getElementById('env-output'); + try { + const res = await fetch('/api/status'); + const env = await res.json(); + envOut.textContent = JSON.stringify(env, null, 2); + } catch(e) { + envOut.textContent = 'Failed to load'; + } +} + +// ---- Init ---- +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.tab-item').forEach(t => + t.addEventListener('click', () => switchTab(t.dataset.tab))); + switchTab('dash'); +});