Devices
Android
Five phones and tablets are full fleet members. They take the same Home Manager modules as a laptop, run supervised services, join the tailnet as their own nodes, host nested Wayland desktops on the phone's GPU, and update from prebuilt closures. Everything runs as an ordinary app, without root.
The shape of it
Nix-on-Droid runs Nix under proot inside an Android app. Collective builds on top of it: a service manager, bridges to the Android side, and the same Home Manager configuration as every other machine.
Services without systemd
There is no init system and Android may kill the app at any time, so services cannot be started once at activation and trusted to stay up. The public supervisord module gives Nix-on-Droid a small service manager. Modules declare programs much as they would declare systemd units, and session start-up idempotently starts whatever is not running.
supervisord.programs.atuin-daemon = { command = "${atuinDaemonStart}"; autostart = true; autorestart = true; startsecs = 0;};
sshd (on port 8022), the userspace Tailscale daemon, a secrets watcher that re-checks SOPS every minute, the shell-history daemon, liveness reporting, a single SSH SOCKS tunnel and the command server all run this way. A Termux:Boot script starts the session when the phone boots.
daemon module declares a long-running service once as daemon.name and renders it as a systemd unit, a launchd daemon or agent, or a supervisord program, at system or home level depending on the platform.Bridges to Android
| Tool | Direction | Does |
|---|---|---|
termux-exec | Nix → Termux | Runs a command in the native Termux app over a local TCP daemon, for things proot cannot do, such as Termux's Android API helpers |
rish | Nix → Shizuku | A shell with Shizuku's elevated permissions, through termux-exec |
nod-install | Nix → Android | Stages an APK where SELinux allows and runs pm install through Shizuku; can also install on another device over SSH |
tasker-run | Nix → Tasker | Triggers a named Tasker task with an am broadcast |
nod-exec | Android → Nix | A local TCP command server for Tasker, home-screen widgets and custom apps, with a pipe mode for one-shot commands and a terminal mode for floating shells |
am-supervisor | Nix → Android | Runs a health check on an interval and, on failure, a restart action such as an am start intent, with exponential backoff |
| PulseServer | Nix → Android | Audio leaves through the native PulseServer app, which owns the speakers and the user's virtual sinks |
Its own tailnet node
Android allows one VPN at a time, and the phone's Tailscale app already holds it. So Nix-on-Droid runs a second, userspace tailscaled under supervisord, as its own node named machine-nod:
- the official ARM64 release, pinned by hash, with its self-updater disabled so Nix owns the version;
- no root, no TUN interface, no route or DNS changes, no exit node and no public Funnel;
- joined with a key from SOPS, and serving a local service with a real certificate through Tailscale Serve.
Userspace Tailscale gives other apps no address to bind and does not route the shell's traffic, so existing Android-side addresses stay in use; the new identity is for serving.
A desktop on the phone
Android owns the physical display and input. Hyprland or Sway runs as a nested Wayland client inside Waylandroid, not as a DRM session, using the same Home Manager module as the laptops with a few platform switches:
- no systemd user manager, so Hyprland's systemd integration is off and Waybar is started by the compositor instead, through a launcher that strips modules needing a system bus, keeps one bar per compositor and restarts it after crashes;
DISPLAYdefaults to:0only when unset, so an inherited display wins;- GPU acceleration through Mesa's turnip Vulkan driver and zink OpenGL. The newest Adreno generation needs a Mesa release newer than Android's pinned nixpkgs, so that generation takes Mesa from the forward-updated pin while the others keep the pinned one.
Termux-X11 and VNC are available for X11 sessions.
Keyboards as code
Layouts for the Unexpected Keyboard app are written in Nix with a small layout library in collective-public, rendered to the app's XML and installed under /etc/unexpected_keyboard/layouts. Variants include a code-oriented QWERTY, a compact one and a window-manager layer.
cltv config keyboard unexpected --layouts code-qwerty --copy
Updates
A phone never builds its own system. Every Android machine has a prebuild assignment, so its closure is built under emulation on an x86 cache node and the phone runs:
cltv switch --cached
That needs SSH to the cache node and a download, no evaluation (see cached activation). Apps released as GitHub release assets are kept current by an APK watcher: named targets with a repository and an asset pattern are polled, downloads are staged, and nod-install installs them.
| Command | Does |
|---|---|
cltv apk status | Watcher state as JSON: releases, staged downloads, failures |
cltv apk check | Poll releases once, stage new APKs and install them |
cltv apk install | Retry installing everything staged |
cltv apk daemon | Run the polling loop in the foreground |