@@ -79,6 +79,11 @@ class CodexServerManager(private val context: Context) {
7979
8080 // ── Install checks ─────────────────────────────────────────────────────
8181
82+ fun isProotInstalled (): Boolean {
83+ val paths = BootstrapInstaller .getPaths(context)
84+ return File (paths.prefixDir, " bin/proot" ).exists()
85+ }
86+
8287 fun isNodeInstalled (): Boolean {
8388 val paths = BootstrapInstaller .getPaths(context)
8489 return File (paths.prefixDir, " bin/node" ).exists()
@@ -177,6 +182,123 @@ WEOF
177182 return isNodeInstalled()
178183 }
179184
185+ /* *
186+ * Install proot from the Termux repository. proot uses ptrace to
187+ * intercept filesystem syscalls and remap hardcoded Termux paths
188+ * (e.g. /data/data/com.termux/files/usr) to our actual prefix,
189+ * enabling dpkg, apt-get install, and other tools that have
190+ * compiled-in path references.
191+ */
192+ fun installProot (onProgress : (String ) -> Unit ): Boolean {
193+ val paths = BootstrapInstaller .getPaths(context)
194+ val prefix = paths.prefixDir
195+ val termuxPrefix = " /data/data/com.termux/files/usr"
196+
197+ onProgress(" Downloading proot…" )
198+
199+ val downloadCmd = """
200+ cd $prefix /tmp &&
201+ apt-get update --allow-insecure-repositories 2>&1;
202+ apt-get download --allow-unauthenticated proot libtalloc 2>&1
203+ """ .trimIndent()
204+
205+ val dlCode = runInPrefix(downloadCmd, onOutput = { onProgress(it) })
206+ if (dlCode != 0 ) {
207+ Log .e(TAG , " apt-get download proot failed with code $dlCode " )
208+ return false
209+ }
210+
211+ onProgress(" Extracting proot…" )
212+ val extractCmd = """
213+ cd $prefix /tmp &&
214+ mkdir -p _proot_stage &&
215+ for deb in proot*.deb libtalloc*.deb; do
216+ [ -f "${' $' } deb" ] && dpkg-deb -x "${' $' } deb" _proot_stage/ 2>&1
217+ done &&
218+ if [ -d "_proot_stage$termuxPrefix " ]; then
219+ cp -a _proot_stage$termuxPrefix /* "$prefix /" 2>&1
220+ elif [ -d "_proot_stage/usr" ]; then
221+ cp -a _proot_stage/usr/* "$prefix /" 2>&1
222+ fi &&
223+ chmod 700 "$prefix /bin/proot" 2>/dev/null
224+ rm -rf _proot_stage proot*.deb libtalloc*.deb 2>/dev/null
225+ echo "proot installed"
226+ """ .trimIndent()
227+
228+ val extractCode = runInPrefix(extractCmd, onOutput = { onProgress(it) })
229+ if (extractCode != 0 ) {
230+ Log .e(TAG , " proot extract failed with code $extractCode " )
231+ return false
232+ }
233+
234+ return isProotInstalled()
235+ }
236+
237+ fun isPythonInstalled (): Boolean {
238+ val paths = BootstrapInstaller .getPaths(context)
239+ return File (paths.prefixDir, " bin/python3" ).exists() ||
240+ File (paths.prefixDir, " bin/python" ).exists()
241+ }
242+
243+ /* *
244+ * Install Python using proot to handle dpkg's hardcoded Termux paths.
245+ * proot bind-mounts our prefix onto the compiled-in Termux prefix so
246+ * dpkg postinst scripts and shared library lookups resolve correctly.
247+ */
248+ fun installPython (onProgress : (String ) -> Unit ): Boolean {
249+ val paths = BootstrapInstaller .getPaths(context)
250+ val prefix = paths.prefixDir
251+ val termuxPrefix = " /data/data/com.termux/files/usr"
252+
253+ onProgress(" Downloading Python packages…" )
254+
255+ val downloadCmd = """
256+ cd $prefix /tmp &&
257+ apt-get update --allow-insecure-repositories 2>&1;
258+ apt-get download --allow-unauthenticated python python-pip 2>&1
259+ """ .trimIndent()
260+
261+ val dlCode = runInPrefix(downloadCmd, onOutput = { onProgress(it) })
262+ if (dlCode != 0 ) {
263+ Log .e(TAG , " apt-get download python failed with code $dlCode " )
264+ }
265+
266+ onProgress(" Extracting Python…" )
267+ val extractCmd = """
268+ cd $prefix /tmp &&
269+ mkdir -p _python_stage &&
270+ for deb in python*.deb; do
271+ [ -f "${' $' } deb" ] && echo "Extracting ${' $' } deb..." && dpkg-deb -x "${' $' } deb" _python_stage/ 2>&1
272+ done &&
273+ if [ -d "_python_stage$termuxPrefix " ]; then
274+ cp -a _python_stage$termuxPrefix /* "$prefix /" 2>&1
275+ elif [ -d "_python_stage/usr" ]; then
276+ cp -a _python_stage/usr/* "$prefix /" 2>&1
277+ fi &&
278+ chmod 700 "$prefix /bin/python"* 2>/dev/null
279+ chmod 700 "$prefix /bin/pip"* 2>/dev/null
280+ rm -rf _python_stage python*.deb 2>/dev/null
281+ echo "Python installed"
282+ """ .trimIndent()
283+
284+ val extractCode = runInPrefix(extractCmd, onOutput = { onProgress(it) })
285+ if (extractCode != 0 ) {
286+ Log .e(TAG , " Python extract failed with code $extractCode " )
287+ return false
288+ }
289+
290+ // Create python3 wrapper to handle shebang issues
291+ val fixCmd = """
292+ if [ -f "$prefix /bin/python3" ] && [ ! -f "$prefix /bin/python" ]; then
293+ ln -sf python3 "$prefix /bin/python"
294+ fi
295+ echo "Python ready"
296+ """ .trimIndent()
297+ runInPrefix(fixCmd, onOutput = { onProgress(it) })
298+
299+ return isPythonInstalled()
300+ }
301+
180302 fun installCodex (onProgress : (String ) -> Unit ): Boolean {
181303 val paths = BootstrapInstaller .getPaths(context)
182304 val prefix = paths.prefixDir
0 commit comments