<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ai2th</title>
    <description>The latest articles on DEV Community by Ai2th (@ai2th).</description>
    <link>https://dev.to/ai2th</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3807739%2Fb37c9ef5-0bb3-4436-ba5c-65d468c26e31.png</url>
      <title>DEV Community: Ai2th</title>
      <link>https://dev.to/ai2th</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ai2th"/>
    <language>en</language>
    <item>
      <title>Linxr | Part 11 — The v3.0.0 Milestone &amp; Official Release</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:38:36 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-11-the-v300-milestone-official-release-2mof</link>
      <guid>https://dev.to/ai2th/linxr-part-11-the-v300-milestone-official-release-2mof</guid>
      <description>&lt;p&gt;After months of intensive engineering across Linux kernel customization, QEMU virtualization tuning, Storage Access Framework (SAF) integration, and Docker container management, we are proud to publish the culmination of our journey: &lt;strong&gt;Linxr v3.0.0!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This final post in our 11-part series brings together all the technical innovations built across the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 The Linxr v3.0.0 Landmark
&lt;/h2&gt;

&lt;p&gt;Linxr turns any arm64 Android device into a rootless Linux workstation and Docker container host. Here is everything packed into our milestone release:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Alpine Linux 3.20 &amp;amp; Kernel 6.18-virt (LTS)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Guest kernel compiled with full preemption (&lt;code&gt;PREEMPT_DYNAMIC&lt;/code&gt;), Virtio drivers (net, blk, rng, 9p), and SLIRP networking.&lt;/li&gt;
&lt;li&gt;OpenRC init, OpenSSH server, &lt;code&gt;sudo&lt;/code&gt;, and full &lt;code&gt;apk&lt;/code&gt; repository access.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Embedded Docker Container Dashboard (Pockr Merged)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Single unified VM hosting both the root shell and the Docker daemon (&lt;code&gt;dockerd&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;45% RAM reduction compared to running separate VM instances.&lt;/li&gt;
&lt;li&gt;Dedicated &lt;strong&gt;Containers&lt;/strong&gt; tab in Flutter to pull images, launch microservices, and stream container logs in real time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Live System &amp;amp; VM Boot Serial Logs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Expandable System &amp;amp; VM Logs console streaming real-time kernel output (&lt;code&gt;dmesg&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt;, &lt;code&gt;sshd&lt;/code&gt;) directly on the Home Screen for 100% transparency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Virtio-9P Storage &amp;amp; SAF Folder Picker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;POSIX file sharing at &lt;code&gt;/storage/emulated/0/LinxrShare&lt;/code&gt; mounted inside guest VM at &lt;code&gt;/mnt/sdcard&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Integrated Storage Access Framework (SAF) folder picker in Settings to select custom host directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. High-Speed Terminal &amp;amp; Instant Typing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TCP_NODELAY&lt;/code&gt; socket tuning eliminating terminal typing latency (&amp;lt;15ms input response).&lt;/li&gt;
&lt;li&gt;Thread-safe SSH ping lock guard ensuring instant auto-reconnection upon boot.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Community &amp;amp; Discussion
&lt;/h2&gt;

&lt;p&gt;Join our official Google Group for updates, feedback, and technical discussions:&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;AI2TH Community:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;(Note: Please check your Spam folder if you haven't received your email invitation!)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📥 Get Linxr v3.0.0
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Get it on Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub Release:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr/releases/tag/v3.0.0%2B26" rel="noopener noreferrer"&gt;https://github.com/AI2TH/Linxr/releases/tag/v3.0.0%2B26&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Official Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io/linxr.html" rel="noopener noreferrer"&gt;https://ai2th.github.io/linxr.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you to everyone in our community for following this engineering journey!&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>docker</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Linxr | Part 10 — Automated End-to-End Testing for Mobile Virtual Machines</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:37:37 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-10-automated-end-to-end-testing-for-mobile-virtual-machines-4298</link>
      <guid>https://dev.to/ai2th/linxr-part-10-automated-end-to-end-testing-for-mobile-virtual-machines-4298</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Part 9&lt;/strong&gt;, we detailed automatic disk expansion. In this installment (&lt;strong&gt;Part 10&lt;/strong&gt;), we share how we built automated End-to-End (E2E) testing pipelines for Android QEMU virtual machines!&lt;/p&gt;




&lt;h2&gt;
  
  
  Automated Quality Assurance for Mobile VMs
&lt;/h2&gt;

&lt;p&gt;Testing a virtual machine running inside an Android app sandbox presents unique challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do you verify QEMU booted cleanly without manual screen inspection?&lt;/li&gt;
&lt;li&gt;How do you measure boot speed benchmarks across different device models?&lt;/li&gt;
&lt;li&gt;How do you validate vCPU and RAM resource limits automatically?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Kotlin Instrumentation Test Pipeline
&lt;/h2&gt;

&lt;p&gt;We created dedicated Android JUnit instrumentation tests (&lt;code&gt;VmE2ETest.kt&lt;/code&gt; and &lt;code&gt;VmResourceTest.kt&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidJUnit4&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VmE2ETest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testVmBootAndSshConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InstrumentationRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstrumentation&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;targetContext&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;vmManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VmManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Start QEMU VM&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;bootSuccess&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vmManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startVm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vCpus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ramMb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VM failed to boot within timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bootSuccess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Execute test command over SSH&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;sshClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SshClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2222&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"alpine"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sshClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uname -a &amp;amp;&amp;amp; docker --version"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Linux"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Docker"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests run automatically on physical test devices via ADB, logging boot timings, verifying SSH socket readiness, and guaranteeing release stability before Play Store uploads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Download Linxr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;https://github.com/AI2TH/Linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>android</category>
      <category>linux</category>
      <category>ci</category>
    </item>
    <item>
      <title>Linxr | Part 9 — Dynamic Storage Expansion &amp; Automatic resize2fs on Boot</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:36:45 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-9-dynamic-storage-expansion-automatic-resize2fs-on-boot-3o5c</link>
      <guid>https://dev.to/ai2th/linxr-part-9-dynamic-storage-expansion-automatic-resize2fs-on-boot-3o5c</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Part 8&lt;/strong&gt;, we solved network latency for instant terminal typing. Today in &lt;strong&gt;Part 9&lt;/strong&gt;, we explain how we implemented dynamic virtual disk expansion (&lt;code&gt;resize2fs&lt;/code&gt;) across reboots without data loss.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Hardcoded 1.4 GB Filesystems
&lt;/h2&gt;

&lt;p&gt;In early Linxr builds, setting 30 GB or 50 GB storage in Settings appeared to allocate a larger QCOW2 virtual disk. However, inside Alpine Linux, running &lt;code&gt;df -h&lt;/code&gt; still reported only 1.4 GB of usable disk space!&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The base rootfs image was formatted as a 1.4 GB ext4 partition.&lt;/li&gt;
&lt;li&gt;Expanding the QCOW2 overlay on Android enlarged the block device, but the guest ext4 filesystem never expanded to fill the new capacity.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Solution: OpenRC diskexpand Service
&lt;/h2&gt;

&lt;p&gt;To automate seamless filesystem expansion:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bundling e2fsprogs-extra:&lt;/strong&gt; Installed &lt;code&gt;resize2fs&lt;/code&gt; into the base Alpine Linux rootfs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boot-Time Auto-Expansion:&lt;/strong&gt; Created a dedicated OpenRC init service (&lt;code&gt;/etc/init.d/diskexpand&lt;/code&gt;) running before &lt;code&gt;sshd&lt;/code&gt; starts:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/sbin/openrc-run&lt;/span&gt;

depend&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    need localmount
    before sshd
&lt;span class="o"&gt;}&lt;/span&gt;

start&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    ebegin &lt;span class="s2"&gt;"Expanding ext4 filesystem to fill virtual disk"&lt;/span&gt;
    resize2fs /dev/vda2 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1
    eend &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;resize2fs&lt;/code&gt; is a fast online operation, it runs in milliseconds on boot. If the disk cap is unchanged, it returns instantly as a no-op; if the user selects 50 GB in Settings, it expands the filesystem dynamically with zero data loss!&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io/linxr.html" rel="noopener noreferrer"&gt;https://ai2th.github.io/linxr.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>storage</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Linxr | Part 8 — Fixing Network Latency &amp; SLIRP Terminal Slowness</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:36:43 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-8-fixing-network-latency-slirp-terminal-slowness-1l7o</link>
      <guid>https://dev.to/ai2th/linxr-part-8-fixing-network-latency-slirp-terminal-slowness-1l7o</guid>
      <description>&lt;p&gt;In earlier parts of our series, we covered SSH terminals, Docker integration, and storage passthrough. Today in &lt;strong&gt;Part 8&lt;/strong&gt;, we share the deep network engineering required to fix terminal latency and optimize SLIRP user-mode networking in QEMU on Android.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Symptom: Terminal Typing Lag
&lt;/h2&gt;

&lt;p&gt;During initial testing, typing commands in the SSH terminal tab felt slightly delayed. Keystrokes would buffer for 100–300ms before rendering on screen.&lt;/p&gt;

&lt;p&gt;When investigating with &lt;code&gt;tcpdump&lt;/code&gt; and socket trace tools, we discovered the root cause:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;QEMU SLIRP User-Mode Stack:&lt;/strong&gt; SLIRP runs entirely in user-space inside the Android process sandbox (no TAP/TUN devices without root).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nagle’s Algorithm &amp;amp; Socket Buffering:&lt;/strong&gt; Small SSH keystroke packets (1 byte) were being delayed by socket buffering mechanisms looking to coalesce small TCP frames.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Fix: TCP_NODELAY &amp;amp; Virtio-Net Tuning
&lt;/h2&gt;

&lt;p&gt;To eliminate typing latency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disabling Nagle's Algorithm:&lt;/strong&gt; Forced &lt;code&gt;TCP_NODELAY&lt;/code&gt; on local SSH port forwards (port 2222) and local proxy bridges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virtio-Net MTU Configuration:&lt;/strong&gt; Tuned guest network interface MTU to 1500 with checksum offloading disabled to reduce guest-to-host packet fragmentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Keystroke response times dropped from 250ms to &lt;strong&gt;under 15ms&lt;/strong&gt;, delivering a desktop-grade shell experience on phone screens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Download &amp;amp; Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub Releases:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr/releases/latest" rel="noopener noreferrer"&gt;https://github.com/AI2TH/Linxr/releases/latest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>networking</category>
      <category>linux</category>
      <category>performance</category>
    </item>
    <item>
      <title>Pockr | Part 7 — Standalone App Deprecated &amp; Merged into Linxr</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:32:19 +0000</pubDate>
      <link>https://dev.to/ai2th/pockr-part-7-standalone-app-deprecated-merged-into-linxr-3o2a</link>
      <guid>https://dev.to/ai2th/pockr-part-7-standalone-app-deprecated-merged-into-linxr-3o2a</guid>
      <description>&lt;p&gt;In earlier parts of the Pockr engineering series, we chronicled our journey bundling native binaries, configuring Linux kernel modules, and executing Docker containers on non-rooted Android hardware.&lt;/p&gt;

&lt;p&gt;Today, we are announcing a major milestone: &lt;strong&gt;Standalone Pockr has officially been merged into Linxr!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Merge Pockr into Linxr?
&lt;/h2&gt;

&lt;p&gt;Running Pockr and Linxr as separate applications required users to run two independent QEMU virtual machine instances:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Double memory overhead (consuming 2 GB+ RAM for two VMs).&lt;/li&gt;
&lt;li&gt;Fragmented workflows between terminal commands and container dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By unifying Pockr into &lt;strong&gt;Linxr v3.0.0&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single Lightweight VM:&lt;/strong&gt; One Alpine Linux 3.20 guest VM hosts both the interactive root shell and the Docker daemon (&lt;code&gt;dockerd&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Containers Tab:&lt;/strong&gt; All container dashboards, image pulling, live container log streaming, and container lifecycle actions are natively accessible inside Linxr.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;45% RAM Reduction:&lt;/strong&gt; Drastically lower memory and CPU usage on mobile processors.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How to Access Pockr Features in Linxr
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Upgrade to &lt;strong&gt;Linxr v3.0.0&lt;/strong&gt; (Build 26).&lt;/li&gt;
&lt;li&gt;Open the app and navigate to the new &lt;strong&gt;Containers&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Pull Docker images, launch microservices, and inspect container logs directly inside Linxr!&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Join the Community &amp;amp; Get Started
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Get Linxr on Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;Linxr GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;https://github.com/AI2TH/Linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;AI2TH Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Official Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io/linxr.html" rel="noopener noreferrer"&gt;https://ai2th.github.io/linxr.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for following the Pockr development journey! All future container &amp;amp; Linux updates will continue in the Linxr series.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>android</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Linxr | Part 7 — Merging Docker Engine &amp; Container Dashboard into Flutter</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:23:45 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-7-merging-docker-engine-container-dashboard-into-flutter-56o6</link>
      <guid>https://dev.to/ai2th/linxr-part-7-merging-docker-engine-container-dashboard-into-flutter-56o6</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Part 6&lt;/strong&gt;, we explored how Virtio-9P and Android SAF enable seamless host-to-guest file sharing. Today in &lt;strong&gt;Part 7&lt;/strong&gt;, we dive into how we merged &lt;strong&gt;Pockr&lt;/strong&gt; into Linxr to create a unified Docker container management dashboard running on non-rooted Android devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Vision: One App for VMs and Containers
&lt;/h2&gt;

&lt;p&gt;Initially, &lt;strong&gt;Pockr&lt;/strong&gt; was built as a standalone Android app for running Docker containers. However, managing two separate QEMU VM instances consumed double the RAM and disk space on mobile devices.&lt;/p&gt;

&lt;p&gt;For Linxr v3.0.0, we unified both projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Guest VM:&lt;/strong&gt; Alpine Linux 3.20 host running both the root shell and the Docker daemon (&lt;code&gt;dockerd&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers Tab:&lt;/strong&gt; A Flutter UI tab for deploying, monitoring, and streaming logs from Docker containers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Architecture: Talking to &lt;code&gt;dockerd&lt;/code&gt; Inside QEMU
&lt;/h2&gt;

&lt;p&gt;To manage Docker containers from Flutter without SSH overhead for every action, we created a lightweight Python API daemon (&lt;code&gt;api_server.py&lt;/code&gt;) running inside Alpine Linux:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Docker Unix Socket Access:&lt;/strong&gt; &lt;code&gt;api_server.py&lt;/code&gt; communicates directly with &lt;code&gt;/var/run/docker.sock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API Endpoints:&lt;/strong&gt; Exposes endpoints for listing containers (&lt;code&gt;GET /containers&lt;/code&gt;), starting/stopping containers (&lt;code&gt;POST /containers/:id/start&lt;/code&gt;), and streaming logs (&lt;code&gt;GET /containers/:id/logs&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter Platform Client:&lt;/strong&gt; Flutter calls local VM API endpoints over port &lt;code&gt;8080&lt;/code&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Flutter Containers UI ] ──( HTTP / port 8080 )──&amp;gt; [ api_server.py ] ──( Unix Socket )──&amp;gt; [ Docker Daemon ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Container Log Streaming in Flutter
&lt;/h2&gt;

&lt;p&gt;Streaming real-time container stdout/stderr logs required a non-blocking chunked response handler in Flutter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;streamContainerLogs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;containerId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'http://127.0.0.1:8080/containers//logs'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stream feeds directly into a reactive Flutter log viewer with auto-scroll and line filtering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results &amp;amp; Benchmark
&lt;/h2&gt;

&lt;p&gt;By merging Pockr into Linxr:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAM Savings:&lt;/strong&gt; Memory usage dropped by &lt;strong&gt;45%&lt;/strong&gt; compared to running separate VM instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified Controls:&lt;/strong&gt; Developers can spin up an &lt;code&gt;nginx&lt;/code&gt; container in the Containers tab and immediately inspect its configuration files via the built-in Terminal tab (&lt;code&gt;linxr:~#&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Download Linxr v3.0.0
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📲 &lt;strong&gt;Google Play:&lt;/strong&gt; &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.ai2th.linxr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub Releases:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr/releases/latest" rel="noopener noreferrer"&gt;https://github.com/AI2TH/Linxr/releases/latest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>android</category>
      <category>flutter</category>
      <category>linux</category>
    </item>
    <item>
      <title>Linxr | Part 6 — Building Virtio-9P Storage Sharing &amp; SAF on Android</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:23:42 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-6-building-virtio-9p-storage-sharing-saf-on-android-37j2</link>
      <guid>https://dev.to/ai2th/linxr-part-6-building-virtio-9p-storage-sharing-saf-on-android-37j2</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Part 5&lt;/strong&gt; of our engineering series, we introduced the high-level features of Linxr v3.0.0. Today in &lt;strong&gt;Part 6&lt;/strong&gt;, we take a deep technical dive into how we solved host-to-guest file sharing on modern Android using &lt;strong&gt;Virtio-9P&lt;/strong&gt; and Android’s &lt;strong&gt;Storage Access Framework (SAF)&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenge: Android Scoped Storage vs Linux Filesystems
&lt;/h2&gt;

&lt;p&gt;Standard Linux virtual machines expect direct POSIX file path access (&lt;code&gt;/home/root/files&lt;/code&gt;, &lt;code&gt;/mnt/storage&lt;/code&gt;). However, Android 10+ enforces strict Scoped Storage rules and granular URI permissions.&lt;/p&gt;

&lt;p&gt;Passing raw host paths to QEMU without root permissions often fails due to Android app sandbox boundaries. We needed a dual-layer solution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Direct high-speed POSIX passthrough for app-managed storage.&lt;/li&gt;
&lt;li&gt;User-configurable folder selection via Android Storage Access Framework (SAF).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Layer 1: Virtio-9P Driver in QEMU
&lt;/h2&gt;

&lt;p&gt;QEMU provides the &lt;strong&gt;9P network filesystem protocol&lt;/strong&gt; (&lt;code&gt;virtio-9p-pci&lt;/code&gt;), which exposes host directory trees to Linux guests with low CPU overhead.&lt;/p&gt;

&lt;p&gt;We configured QEMU command-line parameters in &lt;code&gt;VmManager.kt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;virtioArgs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"-fsdev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"local,id=fsdev0,path=$sharePath,security_model=none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"-device"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"virtio-9p-pci,fsdev=fsdev0,mount_tag=LinxrShare"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside Alpine Linux, an OpenRC startup service automounts this tag on boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; 9p &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;trans&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;virtio,version&lt;span class="o"&gt;=&lt;/span&gt;9p2000.L LinxrShare /mnt/sdcard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any file saved in &lt;code&gt;/storage/emulated/0/LinxrShare&lt;/code&gt; on Android instantly appears inside Alpine Linux under &lt;code&gt;/mnt/sdcard&lt;/code&gt;!&lt;/p&gt;




&lt;h2&gt;
  
  
  Layer 2: Storage Access Framework (SAF) &amp;amp; &lt;code&gt;pickFolder&lt;/code&gt; Channel
&lt;/h2&gt;

&lt;p&gt;To allow users to select custom folders (such as &lt;code&gt;Downloads&lt;/code&gt;, &lt;code&gt;Documents&lt;/code&gt;, or external SD cards), we implemented a native Kotlin &lt;code&gt;MethodChannel&lt;/code&gt; handler (&lt;code&gt;pickFolder&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nc"&gt;MethodChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flutterEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dartExecutor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binaryMessenger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"com.ai2th.linxr/vm"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMethodCallHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"pickFolder"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;intent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ACTION_OPEN_DOCUMENT_TREE&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;addFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_GRANT_READ_URI_PERMISSION&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FLAG_GRANT_WRITE_URI_PERMISSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startActivityForResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;REQUEST_CODE_SAF_PICK_FOLDER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a user selects a folder, Android returns a persistent &lt;code&gt;DocumentFile&lt;/code&gt; URI. We resolve the underlying POSIX path and pass it to QEMU's Virtio-9P filesystem driver.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 7&lt;/strong&gt;, we will explore how we embedded the &lt;strong&gt;Docker Engine&lt;/strong&gt; and built a real-time container management dashboard in Flutter!&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Official Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io/linxr.html" rel="noopener noreferrer"&gt;https://ai2th.github.io/linxr.html&lt;/a&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;strong&gt;Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>storage</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Linxr | Part 5 — Live Boot Logs, SAF Storage &amp; Merging Docker</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Wed, 05 Aug 2026 07:22:47 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-5-live-boot-logs-saf-storage-merging-docker-2naa</link>
      <guid>https://dev.to/ai2th/linxr-part-5-live-boot-logs-saf-storage-merging-docker-2naa</guid>
      <description>&lt;p&gt;In &lt;strong&gt;Part 4&lt;/strong&gt;, we showcased Linxr booting Alpine Linux in QEMU on non-rooted Android devices. Today in &lt;strong&gt;Part 5&lt;/strong&gt;, we dive deep into the major architectural updates that powered our &lt;strong&gt;v3.0.0&lt;/strong&gt; release: bringing live serial boot diagnostics, seamless Storage Access Framework (SAF) file sharing, and embedding full Docker container management directly into Linxr.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Opening the Black Box: Live Serial Log Streaming
&lt;/h2&gt;

&lt;p&gt;In earlier builds, VM startup was a bit of a black box—users saw a spinning loading indicator until port 2222 opened for SSH. If the kernel panicked or a system service hung during boot, there was no visibility inside the app UI.&lt;/p&gt;

&lt;p&gt;To solve this, we redirected Alpine’s kernel console to serial output (&lt;code&gt;/dev/ttyS0&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kernel &amp;amp; OpenRC Logs:&lt;/strong&gt; Intercepted &lt;code&gt;dmesg&lt;/code&gt;, &lt;code&gt;init&lt;/code&gt; scripts, and &lt;code&gt;sshd&lt;/code&gt; startup logs directly from the guest virtual serial console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter UI Integration:&lt;/strong&gt; Added a real-time &lt;strong&gt;System &amp;amp; VM Logs&lt;/strong&gt; console widget on the Home Screen with live auto-scroll, search, and copy-to-clipboard actions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Bridging Host &amp;amp; Guest Storage: Virtio-9P + SAF
&lt;/h2&gt;

&lt;p&gt;Linux virtual machines running under QEMU require clean POSIX filesystem access to exchange files with the Android host system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtio-9P Passthrough:&lt;/strong&gt; Configured QEMU's &lt;code&gt;virtio-9p-pci&lt;/code&gt; driver to expose host directories to the guest OS, mounting &lt;code&gt;/storage/emulated/0/LinxrShare&lt;/code&gt; inside Alpine Linux at &lt;code&gt;/mnt/sdcard&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAF Document Tree Picker:&lt;/strong&gt; Built a Kotlin &lt;code&gt;MethodChannel&lt;/code&gt; handler (&lt;code&gt;pickFolder&lt;/code&gt;) calling Android’s Storage Access Framework (&lt;code&gt;ACTION_OPEN_DOCUMENT_TREE&lt;/code&gt;) to allow users to select custom host folders from Settings.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Merging Pockr: Native Docker Container Dashboard
&lt;/h2&gt;

&lt;p&gt;Previously, container management lived in a separate project named &lt;strong&gt;Pockr&lt;/strong&gt;. For v3.0.0, we unified our architecture by deprecating standalone Pockr and embedding its container controls directly into Linxr:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Containers Tab:&lt;/strong&gt; Inspect running Docker containers, view CPU/RAM usage, and launch containerized microservices (&lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;, &lt;code&gt;alpine&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live Container Logs:&lt;/strong&gt; Stream real-time container stdout/stderr logs from the Docker daemon socket inside the Alpine VM.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Hardened Boot Probing &amp;amp; SSH Lock Guard
&lt;/h2&gt;

&lt;p&gt;When launching QEMU, the OpenSSH daemon takes a few seconds to initialize. Probing TCP port 2222 concurrently from Flutter while QEMU bound socket ports caused intermittent race conditions.&lt;/p&gt;

&lt;p&gt;We introduced a &lt;strong&gt;thread-safe SSH probe lock&lt;/strong&gt; in Kotlin (&lt;code&gt;VmManager.kt&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suppresses redundant socket connection attempts until QEMU signals full network initialization.&lt;/li&gt;
&lt;li&gt;Guarantees instant terminal auto-reconnection as soon as the root shell becomes active.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;Linxr v3.0.0 is officially live on &lt;a href="https://play.google.com/store/apps/details?id=com.ai2th.linxr" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt; and &lt;a href="https://github.com/AI2TH/Linxr/releases/latest" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Join our community for feature requests and discussions:&lt;br&gt;
👉 &lt;strong&gt;Google Group:&lt;/strong&gt; &lt;a href="https://groups.google.com/g/ai2th" rel="noopener noreferrer"&gt;https://groups.google.com/g/ai2th&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>docker</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Linxr | Part 4 — Test Results</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Mon, 09 Mar 2026 17:05:50 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-4-test-results-2fdj</link>
      <guid>https://dev.to/ai2th/linxr-part-4-test-results-2fdj</guid>
      <description>&lt;h1&gt;
  
  
  Test Results
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 4&lt;/strong&gt; of 4 — the final chapter of building Linxr, a single APK that runs Alpine Linux on non-rooted Android.&lt;br&gt;
&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;← Part 3: SSH Terminal in Flutter&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Download
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/AI2TH/Linxr/releases/latest" rel="noopener noreferrer"&gt;linxr-v1.apk&lt;/a&gt;&lt;/strong&gt; — v1.0 · ARM64 · 63 MB&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Requires Android 8.0+ (API 26), ARM64 device. Enable "Install from unknown sources" before sideloading.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How We Test
&lt;/h2&gt;

&lt;p&gt;Testing on a Mac with an Android emulator doesn't work — QEMU inside an emulator needs nested virtualisation, which isn't available.&lt;/p&gt;

&lt;p&gt;We use &lt;strong&gt;Firebase Test Lab&lt;/strong&gt; with an automated Robo test:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;Pixel 2 (arm64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android version&lt;/td&gt;
&lt;td&gt;11 (API 31)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test type&lt;/td&gt;
&lt;td&gt;Robo (automated UI crawler)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout&lt;/td&gt;
&lt;td&gt;600 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Test Result: Pass ✅
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ai2th.github.io/linxr.html" rel="noopener noreferrer"&gt;→ Full test results&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No crashes. Key events from the logcat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;01:29:38  VmManager: startVm()
01:29:38  VmManager: Reusing existing user.qcow2
01:29:38  VmManager: VM process launched

01:29:43  QEMU: OpenRC 0.52.1 is starting up Linux 6.6.14-0-virt (aarch64)
01:29:51  QEMU: * eth0 ... [ ok ]
01:29:52  QEMU: * Starting sshd ... [ ok ]
01:29:52  QEMU: Welcome to Alpine Linux 3.19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From QEMU launch to sshd ready: &lt;strong&gt;~14 seconds&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Robo Test Found
&lt;/h2&gt;

&lt;p&gt;The crawler navigated all three tabs — Home, Terminal, About — and exercised the Start VM / Stop VM flow. It found and read through the full open-source component list in the About screen: dartssh2, xterm, QEMU, Alpine, OpenSSH.&lt;/p&gt;

&lt;p&gt;No crashes. No ANRs. No unexpected states.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Install APK → tap Start VM → Alpine boots in ~14 seconds&lt;/li&gt;
&lt;li&gt;✅ sshd starts automatically; built-in terminal auto-connects&lt;/li&gt;
&lt;li&gt;✅ Persistent overlay — state survives VM restarts&lt;/li&gt;
&lt;li&gt;✅ Internet access — &lt;code&gt;apk add&lt;/code&gt; works inside the VM&lt;/li&gt;
&lt;li&gt;✅ No root. No Termux. No PC required after install.&lt;/li&gt;
&lt;li&gt;✅ Foreground service keeps VM alive when app is backgrounded&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Current Limitations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ARM64 only&lt;/td&gt;
&lt;td&gt;QEMU binary targets &lt;code&gt;aarch64&lt;/code&gt; only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Password auth&lt;/td&gt;
&lt;td&gt;SSH key setup requires manual &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No GUI&lt;/td&gt;
&lt;td&gt;Text terminal only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APK size 63 MB&lt;/td&gt;
&lt;td&gt;Alpine disk image + QEMU + 50 shared libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] SSH key management from the app UI&lt;/li&gt;
&lt;li&gt;[ ] Configurable RAM and vCPU in settings&lt;/li&gt;
&lt;li&gt;[ ] File transfer (SCP/SFTP) from the app&lt;/li&gt;
&lt;li&gt;[ ] ARM32 / x86_64 support&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Full Series
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48"&gt;Part 1: The Idea and Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2: Shipping QEMU in an APK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3: SSH Terminal in Flutter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4: Test Results&lt;/strong&gt; ← you are here&lt;/li&gt;
&lt;/ol&gt;







&lt;h2&gt;
  
  
  Linxr Series — Alpine Linux on Android
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linxr&lt;/strong&gt; = &lt;em&gt;Linux&lt;/em&gt; + &lt;em&gt;r&lt;/em&gt;. A single Android APK that runs a full Alpine Linux shell on any Android phone — no root, no Termux, no PC required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Post&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📖&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-alpine-linux-on-android-no-root-15la"&gt;Intro&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What is Linxr? Start here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48"&gt;Part 1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Idea and Architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shipping QEMU in an APK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSH Terminal in Flutter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-4-test-results-2fdj"&gt;Part 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Test Results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io" rel="noopener noreferrer"&gt;ai2th.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Contributor: Kalvin Nathan&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="mailto:skalvinnathan@gmail.com"&gt;skalvinnathan@gmail.com&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/skalvinnathan/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>testing</category>
      <category>firebase</category>
    </item>
    <item>
      <title>Linxr | Part 3 — SSH Terminal in Flutter</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Mon, 09 Mar 2026 17:03:29 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe</link>
      <guid>https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe</guid>
      <description>&lt;h1&gt;
  
  
  SSH Terminal in Flutter
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 3&lt;/strong&gt; of 4 — building Linxr, a single APK that runs Alpine Linux on non-rooted Android.&lt;br&gt;
&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;← Part 2: Shipping QEMU in an APK&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;Once Alpine boots and sshd starts, the app needs a usable terminal — without requiring an external SSH client.&lt;/p&gt;

&lt;p&gt;Two Flutter packages make this possible:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/dartssh2" rel="noopener noreferrer"&gt;&lt;code&gt;dartssh2&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Pure-Dart SSH2 client — connects to the VM's sshd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://pub.dev/packages/xterm" rel="noopener noreferrer"&gt;&lt;code&gt;xterm&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal emulator widget — renders VT100/xterm sequences&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both are pure Dart. No platform channels, no native code, no extra Android permissions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting to the VM
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;SSHSocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2222&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;seconds:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SSHClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;username:&lt;/span&gt; &lt;span class="s"&gt;'root'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;onPasswordRequest:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;'alpine'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;pty:&lt;/span&gt; &lt;span class="n"&gt;SSHPtyConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;type:&lt;/span&gt; &lt;span class="s"&gt;'xterm-256color'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;viewWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;viewHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SSHPtyConfig&lt;/code&gt; allocates a pseudo-terminal with &lt;code&gt;xterm-256color&lt;/code&gt; — enabling colour output, cursor movement, and correct &lt;code&gt;TERM&lt;/code&gt; variable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring SSH to the Terminal
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// VM output → terminal display&lt;/span&gt;
&lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromCharCodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Keyboard input → SSH session&lt;/span&gt;
&lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onOutput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uint8List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;codeUnits&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Terminal resize → SSH PTY resize&lt;/span&gt;
&lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onResize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_session&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="na"&gt;resizeTerminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resize events keep tools like &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;htop&lt;/code&gt;, and &lt;code&gt;nano&lt;/code&gt; correctly sized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Auto-Reconnect
&lt;/h2&gt;

&lt;p&gt;Alpine takes ~15 seconds to boot. The terminal handles the window where sshd isn't ready yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;_maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 24 × 5s = 2 minutes&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_retryOrError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_retryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_retryCount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_terminal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;$msg&lt;/span&gt;&lt;span class="s"&gt; — retrying in 5s...]&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_scheduleConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;delaySeconds:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Gave up after &lt;/span&gt;&lt;span class="si"&gt;$_maxRetries&lt;/span&gt;&lt;span class="s"&gt; attempts.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Progress messages appear inline in the terminal buffer — no separate loading spinner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Terminal Theme
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="nl"&gt;theme:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;TerminalTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nl"&gt;cursor:&lt;/span&gt;     &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF20C997&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// teal&lt;/span&gt;
    &lt;span class="nl"&gt;foreground:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFE0E0E0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// light grey&lt;/span&gt;
    &lt;span class="nl"&gt;background:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF0E1117&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;// near-black&lt;/span&gt;
    &lt;span class="nl"&gt;green:&lt;/span&gt;      &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF20C997&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;blue:&lt;/span&gt;       &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFF0D6EFD&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;yellow:&lt;/span&gt;     &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFC107&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nl"&gt;red:&lt;/span&gt;        &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFDC3545&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cursor teal (&lt;code&gt;#20C997&lt;/code&gt;) matches the "Connected" status chip.&lt;/p&gt;




&lt;h2&gt;
  
  
  VM-Not-Running Banner
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vmStatus&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'running'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_Banner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;icon:&lt;/span&gt; &lt;span class="n"&gt;Icons&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;warning_amber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;color:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0xFFFFC107&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;message:&lt;/span&gt; &lt;span class="s"&gt;'VM is not running. Start it from the Home tab.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents confusing "Connection refused" errors when the user opens the terminal before starting the VM.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to?"&gt;Part 4 — Test Results&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;/p&gt;







&lt;h2&gt;
  
  
  Linxr Series — Alpine Linux on Android
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linxr&lt;/strong&gt; = &lt;em&gt;Linux&lt;/em&gt; + &lt;em&gt;r&lt;/em&gt;. A single Android APK that runs a full Alpine Linux shell on any Android phone — no root, no Termux, no PC required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Post&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📖&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-alpine-linux-on-android-no-root-15la"&gt;Intro&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What is Linxr? Start here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48"&gt;Part 1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Idea and Architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shipping QEMU in an APK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSH Terminal in Flutter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-4-test-results-2fdj"&gt;Part 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Test Results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io" rel="noopener noreferrer"&gt;ai2th.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>ssh</category>
      <category>android</category>
    </item>
    <item>
      <title>Linxr | Part 2 — Shipping QEMU in an APK</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Mon, 09 Mar 2026 17:03:22 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij</link>
      <guid>https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij</guid>
      <description>&lt;h1&gt;
  
  
  Shipping QEMU in an APK
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt; of 4 — building Linxr, a single APK that runs Alpine Linux on non-rooted Android.&lt;br&gt;
&lt;a href="https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48"&gt;← Part 1: The Idea and Architecture&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Same Wall as Pockr
&lt;/h2&gt;

&lt;p&gt;Getting QEMU to run on non-rooted Android is the same challenge Pockr solved first. If you've read &lt;a href="https://dev.to/ai2th/pockr-part-2-executing-binaries-on-android-3b4k"&gt;Pockr Part 2&lt;/a&gt; and &lt;a href="https://dev.to/ai2th/pockr-part-3-bundling-50-native-libraries-5chh"&gt;Part 3&lt;/a&gt;, you already know the full story. This article gives the overview as it applies to Linxr.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 1: SELinux Blocks execve()
&lt;/h2&gt;

&lt;p&gt;On Android 10+, files in app storage have the SELinux label &lt;code&gt;app_data_file&lt;/code&gt;. That label does not allow &lt;code&gt;execve()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot run program ".../files/qemu/qemu-system-aarch64":
error=13, Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chmod +x&lt;/code&gt; doesn't help — it's a mandatory access control policy, not a Unix permission issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Put QEMU in &lt;code&gt;jniLibs/arm64-v8a/&lt;/code&gt; as a &lt;code&gt;.so&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;qemu-system-aarch64  →  jniLibs/arm64-v8a/libqemu.so
qemu-img             →  jniLibs/arm64-v8a/libqemu_img.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android's &lt;code&gt;PackageManager&lt;/code&gt; installs these to &lt;code&gt;nativeLibraryDir&lt;/code&gt; which has &lt;code&gt;exec_type&lt;/code&gt; SELinux label — &lt;code&gt;execve()&lt;/code&gt; works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 2: Compressed jniLibs
&lt;/h2&gt;

&lt;p&gt;AGP 3.6+ compresses native libraries inside the APK by default — they're loaded directly from the zip, not extracted to disk. QEMU needs to be on disk to execute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix in &lt;code&gt;build.gradle&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;packagingOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jniLibs&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;useLegacyPackaging&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Problem 3: Wrong ELF Interpreter
&lt;/h2&gt;

&lt;p&gt;A self-built QEMU from a Linux desktop uses glibc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android's &lt;code&gt;PackageManager&lt;/code&gt; only extracts &lt;code&gt;.so&lt;/code&gt; files using Android's Bionic linker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Requesting program interpreter: /system/bin/linker64]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use Termux's pre-built QEMU packages — compiled against Bionic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problem 4: 50 Shared Library Dependencies
&lt;/h2&gt;

&lt;p&gt;Termux QEMU dynamically links ~50 shared libraries that live at Termux's prefix — a path that doesn't exist on non-Termux devices. All 50 must be bundled in &lt;code&gt;jniLibs/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two sub-problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Termux embeds a hardcoded &lt;code&gt;RUNPATH&lt;/code&gt;&lt;/strong&gt; pointing to &lt;code&gt;/data/data/com.termux/files/usr/lib/&lt;/code&gt; — Android's linker tries it first and fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;patchelf --remove-rpath&lt;/code&gt; corrupts ELF&lt;/strong&gt; — it restructures LOAD segments, breaking Android 11's strict segment count limits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; An in-place Python script that zeroes only the &lt;code&gt;d_val&lt;/code&gt; field of &lt;code&gt;DT_RPATH&lt;/code&gt;/&lt;code&gt;DT_RUNPATH&lt;/code&gt; ELF entries, leaving the structure intact. See &lt;a href="https://dev.to/ai2th/pockr-part-3-bundling-50-native-libraries-5chh"&gt;Pockr Part 3&lt;/a&gt; for the full implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Linxr vs Pockr: Same Libraries
&lt;/h2&gt;

&lt;p&gt;Linxr uses the same 50-library bundle as Pockr. The only difference in the QEMU launch command is the forwarded port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Linxr: SSH only&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-netdev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user,id=net0,hostfwd=tcp::2222-:22"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Pockr: HTTP API&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-netdev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"user,id=net0,hostfwd=tcp::7080-:7080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3 — SSH Terminal in Flutter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;/p&gt;







&lt;h2&gt;
  
  
  Linxr Series — Alpine Linux on Android
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linxr&lt;/strong&gt; = &lt;em&gt;Linux&lt;/em&gt; + &lt;em&gt;r&lt;/em&gt;. A single Android APK that runs a full Alpine Linux shell on any Android phone — no root, no Termux, no PC required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Post&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📖&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-alpine-linux-on-android-no-root-15la"&gt;Intro&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What is Linxr? Start here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48"&gt;Part 1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Idea and Architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shipping QEMU in an APK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSH Terminal in Flutter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-4-test-results-2fdj"&gt;Part 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Test Results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io" rel="noopener noreferrer"&gt;ai2th.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>kotlin</category>
      <category>security</category>
    </item>
    <item>
      <title>Linxr | Part 1 — The Idea and Architecture</title>
      <dc:creator>Ai2th</dc:creator>
      <pubDate>Mon, 09 Mar 2026 17:01:16 +0000</pubDate>
      <link>https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48</link>
      <guid>https://dev.to/ai2th/linxr-part-1-the-idea-and-architecture-2b48</guid>
      <description>&lt;h1&gt;
  
  
  The Idea and Architecture
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt; of 4 — building Linxr, a single APK that runs Alpine Linux on non-rooted Android.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Constraint
&lt;/h2&gt;

&lt;p&gt;Android is a locked-down Linux system. For regular (non-root) apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;execve()&lt;/code&gt; from app storage is blocked by SELinux W^X policy&lt;/li&gt;
&lt;li&gt;Native Linux tools are unavailable without Termux&lt;/li&gt;
&lt;li&gt;The kernel disables &lt;code&gt;CONFIG_USER_NS&lt;/code&gt; — Docker rootless mode is impossible&lt;/li&gt;
&lt;li&gt;No way to load kernel modules or create network namespaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A QEMU virtual machine sidesteps all of this. The VM runs its own kernel with its own namespace — completely isolated from Android's restrictions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Alpine?
&lt;/h2&gt;

&lt;p&gt;Alpine Linux is the right guest OS for a mobile VM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Alpine&lt;/th&gt;
&lt;th&gt;Debian/Ubuntu&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compressed disk image&lt;/td&gt;
&lt;td&gt;~35 MB&lt;/td&gt;
&lt;td&gt;~300 MB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boot time&lt;/td&gt;
&lt;td&gt;~5 s&lt;/td&gt;
&lt;td&gt;~30+ s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM at idle&lt;/td&gt;
&lt;td&gt;~30 MB&lt;/td&gt;
&lt;td&gt;~100 MB+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package manager&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;apt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Init system&lt;/td&gt;
&lt;td&gt;OpenRC&lt;/td&gt;
&lt;td&gt;systemd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Small footprint, fast boot, full POSIX shell — ideal for embedding in an APK.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Android OS (non-rooted)
└── APK (Flutter + Kotlin)
    ├── VmManager — asset extraction, QEMU lifecycle
    ├── VmService — ForegroundService (keeps QEMU alive)
    └── Flutter UI — Home, Terminal, About tabs
          └── TerminalScreen (dartssh2 + xterm)

QEMU process (libqemu.so)
└── Alpine Linux 3.19 VM
    └── OpenRC init → sshd on :22
          ↑
          SLIRP hostfwd tcp::2222-:22
          ↑
    SSH on 127.0.0.1:2222 (from Android app)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Disk Layout
&lt;/h2&gt;

&lt;p&gt;Linxr uses QCOW2 copy-on-write layering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;base.qcow2 (read-only — Alpine rootfs, openssh baked in)
  └── user.qcow2 (writable overlay — your data lives here)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;base.qcow2&lt;/code&gt; ships compressed in the APK assets. Extracted once on first install.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user.qcow2&lt;/code&gt; is created fresh on first install. Persists across VM restarts — installed packages and files survive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overlay is only recreated when &lt;code&gt;base.qcow2&lt;/code&gt; changes (asset version marker bumps on app update).&lt;/p&gt;




&lt;h2&gt;
  
  
  SLIRP Networking
&lt;/h2&gt;

&lt;p&gt;QEMU SLIRP requires no root and no kernel modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Android app
  └── TCP to 127.0.0.1:2222
        └── QEMU hostfwd: tcp::2222-:22
              └── Alpine VM eth0 (10.0.2.15):22
                    └── sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eth0:    10.0.2.15/24
gateway: 10.0.2.2
DNS:     10.0.2.3  (SLIRP built-in resolver)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VM has full outbound internet access. &lt;code&gt;apk add&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt; all work normally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token-Free Design
&lt;/h2&gt;

&lt;p&gt;Pockr uses a UUID token injected via QEMU kernel cmdline to authenticate its HTTP API. Linxr doesn't need that — SSH handles authentication natively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username: &lt;code&gt;root&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: &lt;code&gt;alpine&lt;/code&gt; (baked into the base image)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No tokens, no custom API — standard SSH.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt; &lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2 — Shipping QEMU in an APK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;/p&gt;







&lt;h2&gt;
  
  
  Linxr Series — Alpine Linux on Android
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Linxr&lt;/strong&gt; = &lt;em&gt;Linux&lt;/em&gt; + &lt;em&gt;r&lt;/em&gt;. A single Android APK that runs a full Alpine Linux shell on any Android phone — no root, no Termux, no PC required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Post&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;📖&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to%%INTRO%%"&gt;Intro&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;What is Linxr? Start here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to%%P1%%"&gt;Part 1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Idea and Architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-2-shipping-qemu-in-an-apk-4kij"&gt;Part 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shipping QEMU in an APK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-3-ssh-terminal-in-flutter-36oe"&gt;Part 3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;SSH Terminal in Flutter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/ai2th/linxr-part-4-test-results-2fdj"&gt;Part 4&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Test Results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/AI2TH/Linxr" rel="noopener noreferrer"&gt;github.com/AI2TH/Linxr&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Website:&lt;/strong&gt; &lt;a href="https://ai2th.github.io" rel="noopener noreferrer"&gt;ai2th.github.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>linux</category>
      <category>qemu</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
