v2.2.5: Shebang patching + proot from nativeLib + Termux RUN_COMMAND + F-Droid fallback
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package ai.z.chat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.getcapacitor.JSObject;
|
||||
@@ -274,12 +277,16 @@ public class BootstrapPlugin extends Plugin {
|
||||
|
||||
private void patchPaths(String dir) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder("find", dir, "-type", "f",
|
||||
"(", "-name", "*.sh", "-o", "-name", "*.conf", "-o", "-name", "*.cfg",
|
||||
"-o", "-name", "*.txt", "-o", "-name", "*.env", "-o", "-name", "properties.sh",
|
||||
"-o", "-name", "profile", "-o", "-name", "bashrc", "-o", "-name", "*.profile", ")");
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
String ourPrefix = filesDir + "/usr";
|
||||
String sedExpr = "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";
|
||||
|
||||
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()));
|
||||
List<String> files = new ArrayList<>();
|
||||
String line;
|
||||
@@ -288,20 +295,20 @@ public class BootstrapPlugin extends Plugin {
|
||||
}
|
||||
p.waitFor();
|
||||
|
||||
String ourPrefix = filesDir + "/usr";
|
||||
for (String filePath : files) {
|
||||
try {
|
||||
ProcessBuilder sedPb = new ProcessBuilder("sed", "-i",
|
||||
"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);
|
||||
ProcessBuilder sedPb = new ProcessBuilder("sed", "-i", sedExpr, filePath);
|
||||
sedPb.start().waitFor();
|
||||
} catch (Exception e) {
|
||||
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",
|
||||
"s|/data/data/com.termux/files/usr|" + ourPrefix + "|g",
|
||||
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) {
|
||||
if (!dir.exists() || !dir.isDirectory()) return;
|
||||
File[] children = dir.listFiles();
|
||||
@@ -337,12 +372,98 @@ public class BootstrapPlugin extends Plugin {
|
||||
chmodRecursive(new File(prefixDir + "/lib"));
|
||||
File etcDir = new File(prefixDir + "/etc");
|
||||
if (etcDir.exists()) chmodRecursive(etcDir);
|
||||
patchShebangs(prefixDir + "/bin");
|
||||
call.resolve(new JSObject().put("fixed", true));
|
||||
} catch (Exception e) {
|
||||
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() {
|
||||
try {
|
||||
File envFile = new File(prefixDir + "/etc/termux.env");
|
||||
|
||||
@@ -54,36 +54,23 @@ public class ShellPlugin extends Plugin {
|
||||
private void refreshShell() {
|
||||
String nativeLibDir = getNativeLibDir();
|
||||
|
||||
File prootInPrefix = new File(prefixDir + "/bin/proot");
|
||||
if (!prootInPrefix.exists() && nativeLibDir != null) {
|
||||
if (nativeLibDir != null) {
|
||||
File bundledProot = new File(nativeLibDir, "libproot.so");
|
||||
File bundledLoader = new File(nativeLibDir, "libproot-loader.so");
|
||||
if (bundledProot.exists()) {
|
||||
try {
|
||||
new File(prefixDir + "/bin").mkdirs();
|
||||
new File(prefixDir + "/libexec").mkdirs();
|
||||
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());
|
||||
prootPath = bundledProot.getAbsolutePath();
|
||||
if (bundledLoader.exists()) {
|
||||
prootLoaderPath = bundledLoader.getAbsolutePath();
|
||||
}
|
||||
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");
|
||||
@@ -98,16 +85,6 @@ public class ShellPlugin extends Plugin {
|
||||
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() {
|
||||
try {
|
||||
return getContext().getApplicationInfo().nativeLibraryDir;
|
||||
|
||||
Reference in New Issue
Block a user