├── doc ├── droid-fake-terminal.png ├── droid-ssh-neofetch.png ├── install.md └── notes.md ├── gengrub.sh ├── flake.nix ├── flake.lock ├── README.md └── configuration.nix /doc/droid-fake-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dramforever/droid-nixos/HEAD/doc/droid-fake-terminal.png -------------------------------------------------------------------------------- /doc/droid-ssh-neofetch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dramforever/droid-nixos/HEAD/doc/droid-ssh-neofetch.png -------------------------------------------------------------------------------- /gengrub.sh: -------------------------------------------------------------------------------- 1 | echo "linux $(realpath result/kernel) $(cat result/kernel-params) init=$(realpath result/init)" 2 | echo "initrd $(realpath result/initrd)" 3 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 3 | 4 | outputs = { self, nixpkgs }: { 5 | nixosConfigurations.droid-nixos = nixpkgs.lib.nixosSystem { 6 | system = "aarch64-linux"; 7 | modules = [ ./configuration.nix ]; 8 | }; 9 | 10 | packages.aarch64-linux.droid-nixos = self.nixosConfigurations.droid-nixos.config.system.build.toplevel; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1742069588, 6 | "narHash": "sha256-C7jVfohcGzdZRF6DO+ybyG/sqpo1h6bZi9T56sxLy+k=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "c80f6a7e10b39afcc1894e02ef785b1ad0b0d7e5", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # droid-nixos 2 | 3 | NixOS VM on the Android "Terminal" app 4 | 5 | **Experimental project**: This thing probably doesn't work. Use at your own risk, especially risk of destroying your virtual machine. 6 | 7 | See also which is probably better anyway. 8 | 9 | ## What on earth is this? 10 | 11 | Google shipped a "Linux development environment" to Pixel devices around March 2025. 12 | 13 | > Starting with Android 15 QPR1, you can try the experimental Linux terminal app that's available on select devices. This app provides access to a Linux terminal environment within a virtual machine (VM). 14 | 15 | Source: 16 | 17 | It runs a Debian VM. We can replace it with NixOS with `/etc/NIXOS_LUSTRATE`. Since it's "just" an AArch64 VM it runs mainline Linux just fine. 18 | 19 | ## Screenshots 20 | 21 | ![Fake terminal running in Terminal app](doc/droid-fake-terminal.png) 22 | 23 | ![Neofetch running on NixOS, while connected via SSH in Termux](doc/droid-ssh-neofetch.png) 24 | 25 | ## What works and what doesn't? 26 | 27 | Works: 28 | 29 | - Boots 30 | - You can SSH into it 31 | 32 | Doesn't work: 33 | 34 | - Bootloader config (`nixos-rebuild switch` won't work) 35 | - Terminal (It's a fake one with just some text now) 36 | 37 | ## More information: 38 | 39 | - [`doc/install.md`](doc/install.md) 40 | - [`doc/notes.md`](doc/notes.md) 41 | -------------------------------------------------------------------------------- /configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | modulesPath, 6 | ... 7 | }: 8 | 9 | { 10 | boot.loader = { 11 | grub.enable = false; 12 | systemd-boot.enable = true; 13 | }; 14 | 15 | boot.initrd.kernelModules = [ 16 | "virtio-pci" 17 | "virtio-blk" 18 | "pci-host-generic" 19 | ]; 20 | 21 | networking.hostName = "droid-nixos"; 22 | 23 | fileSystems."/boot" = { 24 | device = "/dev/vda2"; 25 | fsType = "vfat"; 26 | }; 27 | 28 | fileSystems."/" = { 29 | device = "/dev/vda1"; 30 | fsType = "ext4"; 31 | }; 32 | 33 | # Improve build speed 34 | documentation.nixos.enable = false; 35 | 36 | services.openssh = { 37 | enable = true; 38 | settings = { 39 | PermitRootLogin = "yes"; 40 | PasswordAuthentication = false; 41 | KbdInteractiveAuthentication = false; 42 | }; 43 | }; 44 | 45 | # Run a service on pretending to be ttyd. Without some HTTPS server serving on 46 | # this port, the app kills your VM after some timeout. 47 | services.caddy = { 48 | enable = true; 49 | globalConfig = '' 50 | auto_https disable_redirects # No listening on 80 51 | skip_install_trust # No installing root certs 52 | ''; 53 | virtualHosts.":7681".extraConfig = '' 54 | respond "Welcome to NixOS" 55 | tls internal { 56 | on_demand 57 | } 58 | ''; 59 | }; 60 | 61 | networking.firewall.enable = false; 62 | 63 | users.users.root.openssh.authorizedKeys.keys = [ 64 | # FIXME: Change the key to your own 65 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILyu+hPasdL866eSgC/DgjxFY2swxSJAbI6mgKfPLztg" 66 | ]; 67 | } 68 | -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | **This is not a step by step guide.** It is only a rough documentation of what I tried. 4 | 5 | ## Prerequisites 6 | 7 | As of April 2025, an android device having "Linux development environment" available and enabled in developer settings. It should be available on Pixel 6 and later with up-to-date software. You should have a "Terminal" app. 8 | 9 | ## Installation steps 10 | 11 | All of these steps are done on the guest. 12 | 13 | For convenience, consider using Termux on your host and SSH into the virtual machine. 14 | 15 | ### Install Nix 16 | 17 | ```console 18 | $ sh <(curl -L https://nixos.org/nix/install) --daemon 19 | ``` 20 | 21 | ### Build the config 22 | 23 | Edit `configuration.nix` and change the SSH key to your own. 24 | 25 | In the repo top level directory: 26 | 27 | ``` 28 | $ nix build --extra-experimental-features 'nix-command flakes' -j3 .#droid-nixos 29 | ``` 30 | 31 | You should get a `result` symlink. 32 | 33 | ### Set up first boot 34 | 35 | Set up `/etc/NIXOS_LUSTRATE` 36 | 37 | ``` 38 | $ sudo touch /etc/NIXOS_LUSTRATE 39 | ``` 40 | 41 | **Warning**: From this step onwards, the Debian installation will not be recoverable. If anything goes wrong you may have to turn go to developer settings and turn it off and back on to reset the app. 42 | 43 | Edit `/boot/grub/grub.cfg` to replace the `linux` and `initrd` lines in the default boot entry. For convenience, the `gengrub.sh` file generates the two lines based on the `result` symlink. 44 | 45 | ``` 46 | $ sudo vim /boot/grub/grub.cfg # Or other editor 47 | ``` 48 | 49 | ### Reboot and pray 50 | 51 | ``` 52 | $ sudo reboot 53 | ``` 54 | 55 | Hope that it works! If you get "unrecoverable error", try killing the Terminal app and starting it again, it might work the second time. 56 | 57 | ### Horray 58 | 59 | Instead of the terminal from before, you should see a line of text 60 | 61 | ``` 62 | Welcome to NixOS 63 | ``` 64 | -------------------------------------------------------------------------------- /doc/notes.md: -------------------------------------------------------------------------------- 1 | # Notes on AVF and the Terminal app 2 | 3 | ## Source code 4 | 5 | The source code of everything is available: 6 | 7 | It's not clear what version of this was shipped to Android 15 Pixel phones around March 2025. 8 | 9 | ## crosvm 10 | 11 | Android AVF seems to be based on crosvm. 12 | 13 | ## Terminal 14 | 15 | It's a web terminal, based on ttyd: 16 | 17 | The Terminal app has a WebView essentially showing `https://192.168.0.2:7681`. 18 | 19 | The guest runs ttyd with HTTPS on port 7681 with a self-signed certificate, and authenticates the client (in this case, the Terminal app) using a client certificate, which is hidden in the app, presumably so that other apps cannot just connect to it. 20 | 21 | ## Filesystems 22 | 23 | The OS consists of two partitions: 24 | 25 | - `/dev/vda1`: Ext4 root image 26 | - `/dev/vda2`: FAT32 EFI system partition 27 | 28 | It seems to be assembled into one full GPT image by *something* and provided to the VM as a virtio-blk device. `/dev/vda1` is writable, whereas `/dev/vda2` throws IO errors on write (kernel complains, check `dmesg`) 29 | 30 | Moreover, two Virtiofs file shares are available: 31 | 32 | - `internal` mounted at `/mnt/internal` for communicating files between the VM and the app 33 | - `android` mounted at `/mnt/shared` is the host's download directory 34 | 35 | Funnily, the two images are found in `/mnt/internal`: 36 | 37 | - `/dev/vda1` is `/mnt/internal/root_part` 38 | - `/dev/vda2` is `/mnt/internal/efi_part` 39 | 40 | And `/mnt/internal/efi_part` *is* writable. 41 | 42 | ## gRPC 43 | 44 | The app runs a "debian service". The guest has various daemons talking to it with gRPC. 45 | 46 | ## Networking 47 | 48 | Host is `192.168.0.1`, guest is `192.168.0.2`. 49 | 50 | The guest can get an address via DHCP. 51 | 52 | Port forwarding is implemented in userspace. A daemon in the guest watches open ports in the guest, and notifies the host about them. When you enable forwarding for a port, the host forwards TCP connections to VSOCK, and a guest daemon forwards VSOCK connections to TCP. 53 | --------------------------------------------------------------------------------