v2.2.5: Shebang patching + proot from nativeLib + Termux RUN_COMMAND + F-Droid fallback
This commit is contained in:
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "ai.z.chat"
|
applicationId "ai.z.chat"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 17
|
versionCode 18
|
||||||
versionName "2.2.4"
|
versionName "2.2.5"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package ai.z.chat;
|
package ai.z.chat;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.getcapacitor.JSObject;
|
import com.getcapacitor.JSObject;
|
||||||
@@ -274,12 +277,16 @@ public class BootstrapPlugin extends Plugin {
|
|||||||
|
|
||||||
private void patchPaths(String dir) {
|
private void patchPaths(String dir) {
|
||||||
try {
|
try {
|
||||||
ProcessBuilder pb = new ProcessBuilder("find", dir, "-type", "f",
|
String ourPrefix = filesDir + "/usr";
|
||||||
"(", "-name", "*.sh", "-o", "-name", "*.conf", "-o", "-name", "*.cfg",
|
String sedExpr = "s|/data/data/com.termux/files/usr|" + ourPrefix + "|g;" +
|
||||||
"-o", "-name", "*.txt", "-o", "-name", "*.env", "-o", "-name", "properties.sh",
|
"s|/data/data/com.termux/files/home|" + homeDir + "|g;" +
|
||||||
"-o", "-name", "profile", "-o", "-name", "bashrc", "-o", "-name", "*.profile", ")");
|
"s|/data/data/com.termux|" + filesDir + "|g";
|
||||||
pb.redirectErrorStream(true);
|
|
||||||
Process p = pb.start();
|
ProcessBuilder findPb = new ProcessBuilder("find", dir, "-type", "f",
|
||||||
|
"-not", "-name", "*.so", "-not", "-name", "*.elf",
|
||||||
|
"-not", "-name", "*.pyc", "-not", "-name", "*.a", "-size", "-512k");
|
||||||
|
findPb.redirectErrorStream(true);
|
||||||
|
Process p = findPb.start();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||||
List<String> files = new ArrayList<>();
|
List<String> files = new ArrayList<>();
|
||||||
String line;
|
String line;
|
||||||
@@ -288,20 +295,20 @@ public class BootstrapPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
p.waitFor();
|
p.waitFor();
|
||||||
|
|
||||||
String ourPrefix = filesDir + "/usr";
|
|
||||||
for (String filePath : files) {
|
for (String filePath : files) {
|
||||||
try {
|
try {
|
||||||
ProcessBuilder sedPb = new ProcessBuilder("sed", "-i",
|
ProcessBuilder sedPb = new ProcessBuilder("sed", "-i", sedExpr, filePath);
|
||||||
"s|/data/data/com.termux/files/usr|" + ourPrefix + "|g;" +
|
|
||||||
"s|/data/data/com.termux/files/home|" + homeDir + "|g;" +
|
|
||||||
"s|/data/data/com.termux|" + filesDir + "|g",
|
|
||||||
filePath);
|
|
||||||
sedPb.start().waitFor();
|
sedPb.start().waitFor();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Patch failed for " + filePath + ": " + e.getMessage());
|
Log.w(TAG, "Patch failed for " + filePath + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchShebangs(dir + "/bin");
|
||||||
|
if (new File(dir + "/libexec").exists()) {
|
||||||
|
patchShebangs(dir + "/libexec");
|
||||||
|
}
|
||||||
|
|
||||||
ProcessBuilder ldSo = new ProcessBuilder("sed", "-i",
|
ProcessBuilder ldSo = new ProcessBuilder("sed", "-i",
|
||||||
"s|/data/data/com.termux/files/usr|" + ourPrefix + "|g",
|
"s|/data/data/com.termux/files/usr|" + ourPrefix + "|g",
|
||||||
dir + "/etc/ld.so.conf");
|
dir + "/etc/ld.so.conf");
|
||||||
@@ -311,6 +318,34 @@ public class BootstrapPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void patchShebangs(String dirPath) {
|
||||||
|
File dir = new File(dirPath);
|
||||||
|
if (!dir.exists() || !dir.isDirectory()) return;
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) return;
|
||||||
|
for (File f : files) {
|
||||||
|
if (f.isDirectory()) continue;
|
||||||
|
try {
|
||||||
|
byte[] header = new byte[256];
|
||||||
|
java.io.FileInputStream fis = new java.io.FileInputStream(f);
|
||||||
|
int read = fis.read(header);
|
||||||
|
fis.close();
|
||||||
|
if (read < 2 || header[0] != '#' || header[1] != '!') continue;
|
||||||
|
|
||||||
|
String shebangLine = new String(header, 0, Math.min(read, 256)).split("\n")[0];
|
||||||
|
String newShebang = shebangLine.replaceAll("/data/data/com\\.termux/files/usr/bin/(sh|bash)", "/system/bin/sh")
|
||||||
|
.replaceAll("/data/user/0/ai\\.z\\.chat/files/usr/bin/(sh|bash)", "/system/bin/sh");
|
||||||
|
if (!newShebang.equals(shebangLine)) {
|
||||||
|
ProcessBuilder sedPb = new ProcessBuilder("sed", "-i", "1s|" + shebangLine + "|" + newShebang + "|", f.getAbsolutePath());
|
||||||
|
sedPb.start().waitFor();
|
||||||
|
Log.i(TAG, "Patched shebang: " + f.getName() + " → " + newShebang);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, "Shebang patch failed for " + f.getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void chmodRecursive(File dir) {
|
private void chmodRecursive(File dir) {
|
||||||
if (!dir.exists() || !dir.isDirectory()) return;
|
if (!dir.exists() || !dir.isDirectory()) return;
|
||||||
File[] children = dir.listFiles();
|
File[] children = dir.listFiles();
|
||||||
@@ -337,12 +372,98 @@ public class BootstrapPlugin extends Plugin {
|
|||||||
chmodRecursive(new File(prefixDir + "/lib"));
|
chmodRecursive(new File(prefixDir + "/lib"));
|
||||||
File etcDir = new File(prefixDir + "/etc");
|
File etcDir = new File(prefixDir + "/etc");
|
||||||
if (etcDir.exists()) chmodRecursive(etcDir);
|
if (etcDir.exists()) chmodRecursive(etcDir);
|
||||||
|
patchShebangs(prefixDir + "/bin");
|
||||||
call.resolve(new JSObject().put("fixed", true));
|
call.resolve(new JSObject().put("fixed", true));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
call.reject("Fix permissions failed: " + e.getMessage());
|
call.reject("Fix permissions failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
public void isTermuxInstalled(PluginCall call) {
|
||||||
|
try {
|
||||||
|
getContext().getPackageManager().getPackageInfo("com.termux", 0);
|
||||||
|
call.resolve(new JSObject().put("installed", true).put("prefix", "/data/data/com.termux/files/usr"));
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
call.resolve(new JSObject().put("installed", false).put("prefix", ""));
|
||||||
|
} catch (Exception e) {
|
||||||
|
call.resolve(new JSObject().put("installed", false).put("error", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
public void openTermuxPage(PluginCall call) {
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://f-droid.org/en/packages/com.termux/"));
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
call.resolve(new JSObject().put("opened", true));
|
||||||
|
} catch (Exception e) {
|
||||||
|
call.reject("Failed to open: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
public void runInTermux(PluginCall call) {
|
||||||
|
String command = call.getString("command", "");
|
||||||
|
if (command.isEmpty()) {
|
||||||
|
call.reject("No command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String[] cmd = new String[]{
|
||||||
|
"/system/bin/am", "broadcast",
|
||||||
|
"--user", "0",
|
||||||
|
"-n", "com.termux/com.termux.app.TermuxOpenReceiver",
|
||||||
|
"-a", "com.termux.RUN_COMMAND",
|
||||||
|
"--es", "com.termux.RUN_COMMAND_PATH", "/data/data/com.termux/files/usr/bin/bash",
|
||||||
|
"--es", "com.termux.RUN_COMMAND_ARGUMENTS", "-c " + command,
|
||||||
|
"--ez", "com.termux.RUN_COMMAND_BACKGROUND", "false",
|
||||||
|
"--es", "com.termux.RUN_COMMAND_WORKDIR", "/data/data/com.termux/files/home"
|
||||||
|
};
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(cmd);
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
Process p = pb.start();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) output.append(line).append("\n");
|
||||||
|
p.waitFor(10, java.util.concurrent.TimeUnit.SECONDS);
|
||||||
|
call.resolve(new JSObject().put("sent", true).put("output", output.toString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
call.reject("Termux command failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
public void downloadTermuxApk(PluginCall call) {
|
||||||
|
call.setKeepAlive(true);
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
String packagesContent = downloadToString("https://f-droid.org/repo/index-v1.jar");
|
||||||
|
String apkUrl = null;
|
||||||
|
String[] lines = packagesContent.split("\n");
|
||||||
|
for (String l : lines) {
|
||||||
|
if (l.contains("com.termux") && l.endsWith(".apk")) {
|
||||||
|
apkUrl = l.trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apkUrl == null) {
|
||||||
|
apkUrl = "https://f-droid.org/repo/com.termux_1020.apk";
|
||||||
|
}
|
||||||
|
|
||||||
|
File apkFile = new File(getContext().getCacheDir(), "termux.apk");
|
||||||
|
downloadFile(apkUrl, apkFile, null);
|
||||||
|
|
||||||
|
call.resolve(new JSObject().put("path", apkFile.getAbsolutePath())
|
||||||
|
.put("size", apkFile.length()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
try { call.reject("Download failed: " + e.getMessage()); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
private void writeEnvFile() {
|
private void writeEnvFile() {
|
||||||
try {
|
try {
|
||||||
File envFile = new File(prefixDir + "/etc/termux.env");
|
File envFile = new File(prefixDir + "/etc/termux.env");
|
||||||
|
|||||||
@@ -54,36 +54,23 @@ public class ShellPlugin extends Plugin {
|
|||||||
private void refreshShell() {
|
private void refreshShell() {
|
||||||
String nativeLibDir = getNativeLibDir();
|
String nativeLibDir = getNativeLibDir();
|
||||||
|
|
||||||
File prootInPrefix = new File(prefixDir + "/bin/proot");
|
if (nativeLibDir != null) {
|
||||||
if (!prootInPrefix.exists() && nativeLibDir != null) {
|
|
||||||
File bundledProot = new File(nativeLibDir, "libproot.so");
|
File bundledProot = new File(nativeLibDir, "libproot.so");
|
||||||
File bundledLoader = new File(nativeLibDir, "libproot-loader.so");
|
File bundledLoader = new File(nativeLibDir, "libproot-loader.so");
|
||||||
if (bundledProot.exists()) {
|
if (bundledProot.exists()) {
|
||||||
try {
|
prootPath = bundledProot.getAbsolutePath();
|
||||||
new File(prefixDir + "/bin").mkdirs();
|
if (bundledLoader.exists()) {
|
||||||
new File(prefixDir + "/libexec").mkdirs();
|
prootLoaderPath = bundledLoader.getAbsolutePath();
|
||||||
new File(prefixDir + "/libexec/proot").mkdirs();
|
|
||||||
copyFile(bundledProot, new File(prefixDir + "/bin/proot"));
|
|
||||||
Os.chmod(prefixDir + "/bin/proot", 0755);
|
|
||||||
if (bundledLoader.exists()) {
|
|
||||||
copyFile(bundledLoader, new File(prefixDir + "/libexec/proot/loader"));
|
|
||||||
Os.chmod(prefixDir + "/libexec/proot/loader", 0755);
|
|
||||||
File loader32 = new File(nativeLibDir, "libproot-loader32.so");
|
|
||||||
if (loader32.exists()) {
|
|
||||||
copyFile(loader32, new File(prefixDir + "/libexec/proot/loader32"));
|
|
||||||
Os.chmod(prefixDir + "/libexec/proot/loader32", 0755);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prootPath = prefixDir + "/bin/proot";
|
|
||||||
prootLoaderPath = prefixDir + "/libexec/proot/loader";
|
|
||||||
Log.i(TAG, "Installed bundled proot from APK: " + prootPath);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.w(TAG, "Failed to install bundled proot: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
Log.i(TAG, "Using bundled proot from nativeLib: " + prootPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prootPath == null) {
|
||||||
|
File prootInPrefix = new File(prefixDir + "/bin/proot");
|
||||||
|
if (prootInPrefix.exists()) {
|
||||||
|
prootPath = prootInPrefix.getAbsolutePath();
|
||||||
}
|
}
|
||||||
} else if (prootInPrefix.exists()) {
|
|
||||||
try { Os.chmod(prootInPrefix.getAbsolutePath(), 0755); } catch (Exception e) {}
|
|
||||||
prootPath = prootInPrefix.getAbsolutePath();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File bash = new File(prefixDir + "/bin/bash");
|
File bash = new File(prefixDir + "/bin/bash");
|
||||||
@@ -98,16 +85,6 @@ public class ShellPlugin extends Plugin {
|
|||||||
shellPath = "/system/bin/sh";
|
shellPath = "/system/bin/sh";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyFile(File src, File dst) throws Exception {
|
|
||||||
java.io.FileInputStream fis = new java.io.FileInputStream(src);
|
|
||||||
java.io.FileOutputStream fos = new java.io.FileOutputStream(dst);
|
|
||||||
byte[] buf = new byte[8192];
|
|
||||||
int r;
|
|
||||||
while ((r = fis.read(buf)) > 0) fos.write(buf, 0, r);
|
|
||||||
fos.close();
|
|
||||||
fis.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getNativeLibDir() {
|
private String getNativeLibDir() {
|
||||||
try {
|
try {
|
||||||
return getContext().getApplicationInfo().nativeLibraryDir;
|
return getContext().getApplicationInfo().nativeLibraryDir;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zai-chat",
|
"name": "zai-chat",
|
||||||
"version": "2.2.4",
|
"version": "2.2.5",
|
||||||
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
"description": "Z.AI Chat - Full stack AI chat powered by GLM Coding Plan",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -327,13 +327,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>About</h3>
|
<h3>About</h3>
|
||||||
<p class="about-text">Z.AI Chat v2.2.4</p>
|
<p class="about-text">Z.AI Chat v2.2.5</p>
|
||||||
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
<p class="about-text">Built with Z.AI SDK & GLM-5.1</p>
|
||||||
<p class="about-text">Compatible with Android 15/16</p>
|
<p class="about-text">Compatible with Android 15/16</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h3>Changelog</h3>
|
<h3>Changelog</h3>
|
||||||
<ul class="changelog-list">
|
<ul class="changelog-list">
|
||||||
|
<li>
|
||||||
|
<span class="changelog-version">v2.2.5</span>
|
||||||
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Shebang Patching</strong> — all bin/ scripts patched to use #!/system/bin/sh (bypasses interpreter SELinux)</li>
|
||||||
|
<li><strong>Proot from APK nativeLib</strong> — uses proot directly from APK (apk_data_file SELinux label, always executable)</li>
|
||||||
|
<li><strong>Termux Integration</strong> — detects installed Termux, sends RUN_COMMAND to install tools</li>
|
||||||
|
<li><strong>F-Droid Fallback</strong> — opens Termux F-Droid page if not installed</li>
|
||||||
|
<li><strong>3-Strategy Install</strong> — direct → proot → Termux RUN_COMMAND → manual instructions</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span class="changelog-version">v2.2.4</span>
|
<span class="changelog-version">v2.2.4</span>
|
||||||
<span class="changelog-date">2026-05-19</span>
|
<span class="changelog-date">2026-05-19</span>
|
||||||
|
|||||||
126
www/js/app.js
126
www/js/app.js
@@ -1949,87 +1949,101 @@
|
|||||||
termPrint('[*] This may take a few minutes...', 'info');
|
termPrint('[*] This may take a few minutes...', 'info');
|
||||||
showStatusToast('Installing build tools...', 'info');
|
showStatusToast('Installing build tools...', 'info');
|
||||||
|
|
||||||
var methods = [];
|
var installCmd = 'export PREFIX="' + prefixUsr + '" LD_LIBRARY_PATH="' + prefixUsr + '/lib" && export PATH="' + prefixUsr + '/bin:/system/bin:$PATH" && ';
|
||||||
if (pkgTest.exitCode === 0) {
|
if (pkgTest.exitCode === 0) {
|
||||||
methods.push({cmd: 'export PREFIX="' + prefixUsr + '" PATH="' + prefixUsr + '/bin:$PATH" LD_LIBRARY_PATH="' + prefixUsr + '/lib" && sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1', label: 'pkg'});
|
installCmd += 'sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1';
|
||||||
}
|
} else {
|
||||||
if (aptTest.exitCode === 0) {
|
installCmd += 'sh "' + aptBin + '" update -y 2>&1 && sh "' + aptBin + '" install -y aapt2 ecj dx apksigner 2>&1';
|
||||||
methods.push({cmd: 'export PREFIX="' + prefixUsr + '" PATH="' + prefixUsr + '/bin:$PATH" LD_LIBRARY_PATH="' + prefixUsr + '/lib" && sh "' + aptBin + '" update -y 2>&1 && sh "' + aptBin + '" install -y aapt2 ecj dx apksigner 2>&1', label: 'apt'});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var m = 0; m < methods.length; m++) {
|
termPrint('[*] Strategy 1: Direct execution (patched shebangs)...', 'info');
|
||||||
termPrint('[*] Attempt ' + (m + 1) + ' (' + methods[m].label + ')...', 'info');
|
var result = await shellExec(installCmd, termState.homeDir, false);
|
||||||
var installResult = await shellExec(methods[m].cmd, termState.homeDir, false);
|
if (result.output) {
|
||||||
if (installResult.output) {
|
var out = result.output;
|
||||||
var out = installResult.output;
|
if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
|
||||||
if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
|
termPrint(out.replace(/\n$/, ''), '');
|
||||||
termPrint(out.replace(/\n$/, ''), '');
|
}
|
||||||
}
|
if (await toolsReady()) { termPrint('[OK] Build tools installed!', 'success'); return true; }
|
||||||
|
|
||||||
var recheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
|
termPrint('[*] Strategy 2: Bundled PRoot...', 'info');
|
||||||
if (recheck.exitCode === 0) {
|
if (termState.prootPath) {
|
||||||
termPrint('[OK] Build tools installed successfully!', 'success');
|
termPrint('[OK] PRoot from APK: ' + termState.prootPath, 'success');
|
||||||
showStatusToast('Build tools installed!', 'success');
|
var prootOk = await tryProotExec(termState.prootPath, prefixUsr, pkgBin, aptBin);
|
||||||
termState.devToolsInstalled = true;
|
if (prootOk) return true;
|
||||||
return true;
|
} else {
|
||||||
}
|
termPrint('[!] No bundled PRoot found', 'warning');
|
||||||
}
|
}
|
||||||
|
|
||||||
termPrint('[*] Direct execution failed. Trying PRoot workaround...', 'info');
|
termPrint('[*] Strategy 3: Termux RUN_COMMAND...', 'info');
|
||||||
var prootOk = await tryProotInstall(prefixUsr, pkgBin, aptBin);
|
var termuxOk = await tryTermuxInstall();
|
||||||
if (prootOk) return true;
|
if (termuxOk) return true;
|
||||||
|
|
||||||
termPrint('', '');
|
termPrint('', '');
|
||||||
termPrint('[!] Auto-install failed. Android SELinux may be blocking binary execution.', 'err');
|
termPrint('[!] All auto-install strategies failed.', 'err');
|
||||||
termPrint('[*] Fallback options:', 'warning');
|
termPrint('[*] Manual fix: Install Termux from F-Droid:', 'warning');
|
||||||
termPrint(' 1. Install Termux from F-Droid, then run:', 'warning');
|
termPrint(' 1. Open: https://f-droid.org/en/packages/com.termux/', 'warning');
|
||||||
termPrint(' pkg install aapt2 ecj dx apksigner', 'warning');
|
termPrint(' 2. Install Termux, open it, run:', 'warning');
|
||||||
termPrint(' 2. Z.AI Chat will auto-detect Termux tools.', 'warning');
|
termPrint(' pkg update && pkg install aapt2 ecj dx apksigner', 'warning');
|
||||||
|
termPrint(' 3. Restart Z.AI Chat — tools will be detected.', 'warning');
|
||||||
termState.devToolsInstalled = false;
|
termState.devToolsInstalled = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryProotInstall(prefixUsr, pkgBin, aptBin) {
|
async function toolsReady() {
|
||||||
var prootCmd = termState.prootPath;
|
var recheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
|
||||||
if (!prootCmd) {
|
if (recheck.exitCode === 0) {
|
||||||
try {
|
showStatusToast('Build tools installed!', 'success');
|
||||||
var prootResult = await Bootstrap.installProot();
|
termState.devToolsInstalled = true;
|
||||||
if (prootResult && prootResult.path) {
|
return true;
|
||||||
prootCmd = prootResult.path;
|
|
||||||
termState.prootPath = prootCmd;
|
|
||||||
termState.hasProot = true;
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
termPrint('[!] PRoot download failed: ' + e.message, 'err');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!prootCmd) {
|
async function tryProotExec(prootCmd, prefixUsr, pkgBin, aptBin) {
|
||||||
termPrint('[!] No PRoot available', 'err');
|
var pkgCmd = 'sh /usr/bin/pkg update -y 2>&1 && sh /usr/bin/pkg install -y aapt2 ecj dx apksigner 2>&1';
|
||||||
return false;
|
if (!new File(pkgBin).exists) pkgCmd = 'sh /usr/bin/apt update -y 2>&1 && sh /usr/bin/apt install -y aapt2 ecj dx apksigner 2>&1';
|
||||||
}
|
|
||||||
|
|
||||||
termPrint('[OK] PRoot available: ' + prootCmd, 'success');
|
|
||||||
var pkgCmd = 'sh "' + pkgBin + '" update -y 2>&1 && sh "' + pkgBin + '" install -y aapt2 ecj dx apksigner 2>&1';
|
|
||||||
var wrappedCmd = prootCmd + ' -0 -b /dev -b /proc -b /sys -r ' + prefixUsr + ' /bin/sh -c \'' + pkgCmd.replace(/'/g, "'\\''") + '\'';
|
var wrappedCmd = prootCmd + ' -0 -b /dev -b /proc -b /sys -r ' + prefixUsr + ' /bin/sh -c \'' + pkgCmd.replace(/'/g, "'\\''") + '\'';
|
||||||
|
|
||||||
termPrint('[*] Installing via PRoot...', 'info');
|
termPrint('[*] Running pkg via PRoot...', 'info');
|
||||||
var result = await shellExec(wrappedCmd, termState.homeDir, false);
|
var result = await shellExec(wrappedCmd, termState.homeDir, false);
|
||||||
if (result.output) {
|
if (result.output) {
|
||||||
var out = result.output;
|
var out = result.output;
|
||||||
if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
|
if (out.length > 2000) out = out.substring(0, 1000) + '\n... truncated ...\n' + out.substring(out.length - 800);
|
||||||
termPrint(out.replace(/\n$/, ''), '');
|
termPrint(out.replace(/\n$/, ''), '');
|
||||||
}
|
}
|
||||||
|
if (result.exitCode === 0 || result.output.indexOf('Setting up') !== -1) {
|
||||||
|
if (await toolsReady()) return true;
|
||||||
|
}
|
||||||
|
termPrint('[!] PRoot strategy failed (exit ' + result.exitCode + ')', 'err');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var recheck = await shellExec('command -v aapt2 >/dev/null 2>&1 && command -v ecj >/dev/null 2>&1', termState.homeDir, false);
|
async function tryTermuxInstall() {
|
||||||
if (recheck.exitCode === 0) {
|
if (!Bootstrap) return false;
|
||||||
termPrint('[OK] Build tools installed via PRoot!', 'success');
|
var termuxInfo;
|
||||||
showStatusToast('Build tools installed!', 'success');
|
try { termuxInfo = await Bootstrap.isTermuxInstalled(); } catch(e) { return false; }
|
||||||
termState.devToolsInstalled = true;
|
|
||||||
return true;
|
if (!termuxInfo.installed) {
|
||||||
|
termPrint('[!] Termux not installed. Opening F-Droid...', 'warning');
|
||||||
|
try { await Bootstrap.openTermuxPage(); } catch(e) {}
|
||||||
|
termPrint('[*] After installing Termux:', 'info');
|
||||||
|
termPrint(' 1. Open Termux app', 'info');
|
||||||
|
termPrint(' 2. Run: pkg update && pkg install aapt2 ecj dx apksigner', 'info');
|
||||||
|
termPrint(' 3. Come back to Z.AI Chat', 'info');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
termPrint('[!] PRoot execution result: exit code ' + result.exitCode, 'err');
|
termPrint('[OK] Termux detected! Sending install command...', 'success');
|
||||||
|
try {
|
||||||
|
var runResult = await Bootstrap.runInTermux({command: 'pkg update -y && pkg install -y aapt2 ecj dx apksigner'});
|
||||||
|
termPrint('[*] Command sent to Termux. Waiting...', 'info');
|
||||||
|
await new Promise(function(r) { setTimeout(r, 15000); });
|
||||||
|
if (await toolsReady()) return true;
|
||||||
|
await new Promise(function(r) { setTimeout(r, 30000); });
|
||||||
|
if (await toolsReady()) return true;
|
||||||
|
} catch(e) {
|
||||||
|
termPrint('[!] Termux RUN_COMMAND failed: ' + e.message, 'err');
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user