v2.3.0: Java virtual environment via app_process, SELinux bypass for build tools

This commit is contained in:
admin
2026-05-20 12:28:24 +04:00
Unverified
parent 71a26e259d
commit f86a5added
10 changed files with 417 additions and 87 deletions

View File

@@ -12,23 +12,24 @@
};
var BUILD_SCRIPT = [
'#!/bin/sh',
'#!/system/bin/sh',
'set -e',
'PROJECT_DIR=$(pwd)',
'BUILD_DIR="$PROJECT_DIR/build"',
'',
'AAPT2=$(command -v aapt2 2>/dev/null)',
'ECJ=$(command -v ecj 2>/dev/null)',
'D8=$(command -v d8 2>/dev/null)',
'DX=$(command -v dx 2>/dev/null)',
'APKSIGNER=$(command -v apksigner 2>/dev/null)',
'AAPT2=$(command -v aapt2 2>/dev/null || echo "$TOOLS/bin/aapt2")',
'ECJ=$(command -v ecj 2>/dev/null || echo "$TOOLS/bin/ecj")',
'D8=$(command -v d8 2>/dev/null || echo "$TOOLS/bin/d8")',
'APKSIGNER=$(command -v apksigner 2>/dev/null || echo "$TOOLS/bin/apksigner")',
'',
'if [ -z "$AAPT2" ]; then echo "[BUILD FAILED] aapt2 not found. Install: pkg install aapt2"; exit 1; fi',
'if [ -z "$ECJ" ]; then echo "[BUILD FAILED] ecj not found. Install: pkg install ecj"; exit 1; fi',
'if [ -z "$D8" ] && [ -z "$DX" ]; then echo "[BUILD FAILED] d8/dx not found. Install: pkg install d8"; exit 1; fi',
'if [ ! -x "$AAPT2" ] && [ ! -f "$AAPT2" ]; then echo "[BUILD FAILED] aapt2 not found"; exit 1; fi',
'if [ ! -f "$ECJ" ]; then echo "[BUILD FAILED] ecj not found"; exit 1; fi',
'if [ ! -f "$D8" ]; then echo "[BUILD FAILED] d8 not found"; exit 1; fi',
'',
'ANDROID_JAR=$(dirname "$AAPT2")/../share/aapt2/android.jar',
'if [ ! -f "$ANDROID_JAR" ]; then echo "[BUILD FAILED] android.jar not found at $ANDROID_JAR"; exit 1; fi',
'if [ ! -f "$ANDROID_JAR" ]; then ANDROID_JAR="$TOOLS/share/android.jar"; fi',
'if [ ! -f "$ANDROID_JAR" ]; then ANDROID_JAR="$PREFIX/share/aapt2/android.jar"; fi',
'if [ ! -f "$ANDROID_JAR" ]; then echo "[BUILD FAILED] android.jar not found"; exit 1; fi',
'',
'rm -rf "$BUILD_DIR"',
'mkdir -p "$BUILD_DIR/gen" "$BUILD_DIR/classes" "$BUILD_DIR/apk"',
@@ -56,16 +57,12 @@
'find app/src/main/java -name "*.java" >> "$BUILD_DIR/sources.txt" 2>/dev/null',
'find "$BUILD_DIR/gen" -name "*.java" >> "$BUILD_DIR/sources.txt" 2>/dev/null',
'if [ ! -s "$BUILD_DIR/sources.txt" ]; then echo "[BUILD FAILED] No Java source files found"; exit 1; fi',
'"$ECJ" -source 11 -target 11 -classpath "$ANDROID_JAR" -d "$BUILD_DIR/classes" @"$BUILD_DIR/sources.txt" 2>&1 || { echo "[BUILD FAILED] Java compilation failed"; exit 1; }',
'sh "$ECJ" -source 11 -target 11 -classpath "$ANDROID_JAR" -d "$BUILD_DIR/classes" @"$BUILD_DIR/sources.txt" 2>&1 || { echo "[BUILD FAILED] Java compilation failed"; exit 1; }',
'',
'echo "[*] Converting to DEX..."',
'find "$BUILD_DIR/classes" -name "*.class" > "$BUILD_DIR/classfiles.txt"',
'if [ ! -s "$BUILD_DIR/classfiles.txt" ]; then echo "[BUILD FAILED] No class files compiled"; exit 1; fi',
'if [ -n "$D8" ]; then',
' "$D8" --output "$BUILD_DIR" @"$BUILD_DIR/classfiles.txt" 2>&1 || { echo "[BUILD FAILED] D8 failed"; exit 1; }',
'else',
' "$DX" --output "$BUILD_DIR/classes.dex" @"$BUILD_DIR/classfiles.txt" 2>&1 || { echo "[BUILD FAILED] DX failed"; exit 1; }',
'fi',
'sh "$D8" --output "$BUILD_DIR" @"$BUILD_DIR/classfiles.txt" 2>&1 || { echo "[BUILD FAILED] D8 failed"; exit 1; }',
'if [ ! -f "$BUILD_DIR/classes.dex" ]; then echo "[BUILD FAILED] classes.dex not found"; exit 1; fi',
'',
'echo "[*] Packaging..."',
@@ -93,20 +90,12 @@
' fi',
'fi',
'',
'if [ -f "$KEYSTORE" ] && [ -n "$APKSIGNER" ]; then',
' "$APKSIGNER" sign --ks "$KEYSTORE" --ks-pass pass:android --ks-key-alias androiddebugkey --key-pass pass:android "$BUILD_DIR/app.unaligned.apk" 2>&1 || true',
' mv "$BUILD_DIR/app.unaligned.apk" "$BUILD_DIR/app-signed.apk"',
'elif [ -n "$APKSIGNER" ]; then',
' mv "$BUILD_DIR/app.unaligned.apk" "$BUILD_DIR/app-signed.apk"',
' echo "[!] Warning: APK unsigned - no keystore"',
'else',
' mv "$BUILD_DIR/app.unaligned.apk" "$BUILD_DIR/app-signed.apk"',
' echo "[!] Warning: APK unsigned - no apksigner"',
'fi',
'sh "$APKSIGNER" sign --ks "$KEYSTORE" --ks-pass pass:android --ks-key-alias androiddebugkey --key-pass pass:android "$BUILD_DIR/app.unaligned.apk" 2>&1 || true',
'mv "$BUILD_DIR/app.unaligned.apk" "$BUILD_DIR/app-signed.apk"',
'',
'APK_PATH="$BUILD_DIR/app-signed.apk"',
'APK_SIZE=$(du -h "$APK_PATH" 2>/dev/null | cut -f1)',
'echo "[BUILD OK] APK: $APK_PATH ($APK_SIZE)"',
'echo "[BUILD OK] APK: $APK_PATH ($APK_SIZE)"'
].join('\n');
var state = {
@@ -1182,8 +1171,10 @@
activePid: null,
activeStreamId: null,
devToolsInstalled: false,
javaToolsInstalled: false,
hasProot: false,
prootPath: '',
nativeLibDir: '',
commandQueue: []
};
@@ -1913,7 +1904,15 @@
return true;
}
try { await Bootstrap.fixPermissions(); } catch(e) {}
var javaCheck = await shellExec('test -f "$TOOLS/jars/ecj.jar" && test -f "$TOOLS/jars/d8.jar" && test -f "$TOOLS/jars/apksigner.jar"', termState.homeDir, false);
if (javaCheck.exitCode === 0 && termState.nativeLibDir) {
var aapt2Check = await shellExec('test -f "$TOOLS/bin/aapt2"', termState.homeDir, false);
if (aapt2Check.exitCode === 0) {
termState.devToolsInstalled = true;
termState.javaToolsInstalled = true;
return true;
}
}
if (Shell) {
var env = await Shell.getEnv();
@@ -1923,79 +1922,162 @@
termState.cwd = env.CWD || env.HOME;
termState.hasProot = env.hasProot === true;
termState.prootPath = env.prootPath || '';
termState.nativeLibDir = env.nativeLibDir || '';
}
var prefix = termState.homeDir ? termState.homeDir.replace('/home', '') : termState.homeDir || '';
var prefixUsr = prefix + '/usr';
var pkgBin = prefixUsr + '/bin/pkg';
var aptBin = prefixUsr + '/bin/apt';
var pkgTest = await shellExec('test -f "' + pkgBin + '"', termState.homeDir, false);
var aptTest = await shellExec('test -f "' + aptBin + '"', termState.homeDir, false);
if (pkgTest.exitCode !== 0 && aptTest.exitCode !== 0) {
var bsStatus;
try { bsStatus = await Bootstrap.getStatus(); } catch(e) { bsStatus = { installed: false }; }
if (!bsStatus.installed) {
termPrint('[!] Termux bootstrap not installed yet.', 'err');
} else {
termPrint('[!] Package manager not found at ' + pkgBin, 'err');
}
termState.devToolsInstalled = false;
return false;
}
termPrint('[*] Installing build tools (aapt2, ecj, dx, apksigner)...', 'info');
termPrint('[*] This may take a few minutes...', 'info');
termPrint('[*] Setting up Java build tools (app_process virtual JVM)...', 'info');
showStatusToast('Installing build tools...', 'info');
var installCmd = 'export PREFIX="' + prefixUsr + '" LD_LIBRARY_PATH="' + prefixUsr + '/lib" && export PATH="' + prefixUsr + '/bin:/system/bin:$PATH" && ';
if (pkgTest.exitCode === 0) {
installCmd += 'sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1';
} else {
installCmd += 'sh "' + aptBin + '" update -y 2>&1 && sh "' + aptBin + '" install -y aapt2 ecj dx apksigner 2>&1';
}
var javaOk = await setupJavaTools();
if (javaOk) return true;
termPrint('[*] Strategy 1: Direct execution (patched shebangs)...', 'info');
var result = await shellExec(installCmd, termState.homeDir, false);
if (result.output) {
var out = result.output;
if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
termPrint(out.replace(/\n$/, ''), '');
}
if (await toolsReady()) { termPrint('[OK] Build tools installed!', 'success'); return true; }
var prefix = termState.homeDir ? termState.homeDir.replace('/home', '') : '';
var prefixUsr = prefix + '/usr';
try { await Bootstrap.fixPermissions(); } catch(e) {}
termPrint('[*] Trying Termux pkg install...', 'info');
var pkgOk = await tryPkgInstall(prefixUsr);
if (pkgOk) return true;
termPrint('[*] Strategy 2: Bundled PRoot...', 'info');
if (termState.prootPath) {
termPrint('[OK] PRoot from APK: ' + termState.prootPath, 'success');
var prootOk = await tryProotExec(termState.prootPath, prefixUsr, pkgBin, aptBin);
termPrint('[*] Trying PRoot...', 'info');
var prootOk = await tryProotExec(termState.prootPath, prefixUsr, prefixUsr + '/bin/pkg', prefixUsr + '/bin/apt');
if (prootOk) return true;
} else {
termPrint('[!] No bundled PRoot found', 'warning');
}
termPrint('[*] Strategy 3: Termux RUN_COMMAND...', 'info');
termPrint('[*] Checking for Termux...', 'info');
var termuxOk = await tryTermuxInstall();
if (termuxOk) return true;
termPrint('', '');
termPrint('[!] All auto-install strategies failed.', 'err');
termPrint('[*] Manual fix: Install Termux from F-Droid:', 'warning');
termPrint(' 1. Open: https://f-droid.org/en/packages/com.termux/', 'warning');
termPrint(' 2. Install Termux, open it, run:', 'warning');
termPrint(' pkg update && pkg install aapt2 ecj dx apksigner', 'warning');
termPrint(' 3. Restart Z.AI Chat — tools will be detected.', 'warning');
termPrint('[!] All strategies failed. Install Termux from F-Droid:', 'err');
termPrint(' https://f-droid.org/en/packages/com.termux/', 'warning');
termPrint(' Then: pkg update && pkg install aapt2 ecj dx apksigner', 'warning');
termState.devToolsInstalled = false;
return false;
}
async function setupJavaTools() {
if (!Shell || !Bootstrap) return false;
var toolsDir = termState.toolsDir;
var jarsDir = toolsDir + '/jars';
var binDir = toolsDir + '/bin';
var nativeLibDir = termState.nativeLibDir;
await shellExec('mkdir -p "' + jarsDir + '" "' + binDir + '"', termState.homeDir, false);
termPrint('[*] Extracting bundled JARs from APK assets...', 'info');
try {
var extractResult = await Bootstrap.extractAsset({src: 'jars/ecj.jar', dest: jarsDir + '/ecj.jar'});
termPrint('[OK] ecj.jar extracted (' + Math.round(extractResult.size/1024) + ' KB)', 'success');
} catch(e) {
termPrint('[!] ecj.jar extract failed: ' + e.message, 'err');
return false;
}
try {
var extractResult = await Bootstrap.extractAsset({src: 'jars/apksigner.jar', dest: jarsDir + '/apksigner.jar'});
termPrint('[OK] apksigner.jar extracted (' + Math.round(extractResult.size/1024) + ' KB)', 'success');
} catch(e) {
termPrint('[!] apksigner.jar extract failed: ' + e.message, 'err');
return false;
}
var d8Test = await shellExec('test -f "' + jarsDir + '/d8.jar"', termState.homeDir, false);
if (d8Test.exitCode !== 0) {
termPrint('[*] Downloading d8.jar (DEX compiler, ~18MB)...', 'info');
try {
var dlResult = await Bootstrap.downloadFile({url: 'https://dl.google.com/android/repository/build-tools_r36-linux.zip', dest: toolsDir + '/build-tools.zip'});
termPrint('[*] Extracting d8.jar from build-tools...', 'info');
await shellExec('cd "' + toolsDir + '" && unzip -o build-tools.zip "*/lib/d8.jar" 2>&1 && mv */lib/d8.jar jars/d8.jar && rm -rf build-tools.zip android-*', termState.homeDir, false);
} catch(e) {
termPrint('[!] d8.jar download failed: ' + e.message, 'warning');
termPrint('[*] Trying Termux dx package...', 'info');
try {
var dxResult = await Bootstrap.installProot();
} catch(e2) {}
}
}
var d8Check = await shellExec('test -f "' + jarsDir + '/d8.jar" && test -s "' + jarsDir + '/d8.jar"', termState.homeDir, false);
if (d8Check.exitCode !== 0) {
termPrint('[!] d8.jar not available', 'err');
return false;
}
termPrint('[OK] d8.jar ready', 'success');
var aapt2Check = await shellExec('test -f "' + binDir + '/aapt2"', termState.homeDir, false);
if (aapt2Check.exitCode !== 0) {
termPrint('[*] Installing aapt2 from Termux repo...', 'info');
try {
var aapt2Result = await Bootstrap.installAapt2({toolsDir: toolsDir});
termPrint('[OK] aapt2 installed (' + Math.round(aapt2Result.size/1024) + ' KB)', 'success');
} catch(e) {
termPrint('[!] aapt2 install failed: ' + e.message, 'warning');
termPrint('[*] Build will work for resource-less APKs only', 'info');
}
}
termPrint('[*] Creating app_process wrapper scripts...', 'info');
var wrappers = {
ecj: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -Djava.class.path=' + jarsDir + '/ecj.jar org.eclipse.jdt.internal.compiler.batch.Main "$@"',
d8: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -Djava.class.path=' + jarsDir + '/d8.jar com.android.tools.r8.D8 "$@"',
apksigner: '#!/system/bin/sh\nexec /system/bin/app_process /system/bin --nice-name=zaichat -jar ' + jarsDir + '/apksigner.jar "$@"'
};
for (var name in wrappers) {
try {
await Shell.writeFile({path: binDir + '/' + name, content: wrappers[name]});
await shellExec('chmod 755 "' + binDir + '/' + name + '"', termState.homeDir, false);
} catch(e) {
termPrint('[!] Failed to create ' + name + ' wrapper: ' + e.message, 'err');
}
}
termPrint('[OK] Wrapper scripts created (ecj, d8, apksigner)', 'success');
var verify = await shellExec('test -f "' + jarsDir + '/ecj.jar" && test -f "' + jarsDir + '/d8.jar" && test -f "' + jarsDir + '/apksigner.jar" && test -x "' + binDir + '/ecj" && test -x "' + binDir + '/aapt2"', termState.homeDir, false);
if (verify.exitCode === 0) {
termPrint('[OK] Java build environment ready!', 'success');
showStatusToast('Build tools ready!', 'success');
termState.devToolsInstalled = true;
termState.javaToolsInstalled = true;
return true;
}
termPrint('[OK] Java tools ready (aapt2 optional)', 'success');
termState.devToolsInstalled = true;
termState.javaToolsInstalled = true;
return true;
}
async function tryPkgInstall(prefixUsr) {
var pkgBin = prefixUsr + '/bin/pkg';
var pkgTest = await shellExec('test -f "' + pkgBin + '"', termState.homeDir, false);
if (pkgTest.exitCode !== 0) return false;
var cmd = 'export PREFIX="' + prefixUsr + '" LD_LIBRARY_PATH="' + prefixUsr + '/lib" PATH="' + prefixUsr + '/bin:/system/bin:$PATH" && sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1';
var result = await shellExec(cmd, termState.homeDir, false);
if (result.output && result.output.length > 200) {
termPrint(result.output.substring(result.output.length - 200), '');
}
return await toolsReady();
}
async function toolsReady() {
var recheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
if (recheck.exitCode === 0) {
var termuxCheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
if (termuxCheck.exitCode === 0) {
showStatusToast('Build tools installed!', 'success');
termState.devToolsInstalled = true;
return true;
}
if (termState.toolsDir) {
var javaCheck = await shellExec('test -f "' + termState.toolsDir + '/jars/ecj.jar" && test -f "' + termState.toolsDir + '/jars/d8.jar"', termState.homeDir, false);
if (javaCheck.exitCode === 0) {
termState.devToolsInstalled = true;
termState.javaToolsInstalled = true;
return true;
}
}
return false;
}
@@ -2061,13 +2143,22 @@
if (termState.devToolsInstalled) return;
var check = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
if (check.exitCode === 0) {
var termuxCheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
if (termuxCheck.exitCode === 0) {
termState.devToolsInstalled = true;
return;
}
showDevToolsBanner('Build tools (aapt2, ecj, d8) not installed. Tap to auto-install.');
if (termState.toolsDir) {
var javaCheck = await shellExec('test -f "' + termState.toolsDir + '/jars/ecj.jar" && test -f "' + termState.toolsDir + '/jars/d8.jar"', termState.homeDir, false);
if (javaCheck.exitCode === 0) {
termState.devToolsInstalled = true;
termState.javaToolsInstalled = true;
return;
}
}
showDevToolsBanner('Build tools not installed. Tap to auto-install via Java virtual environment.');
}
function showDevToolsBanner(msg) {