AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Jellyfin, a NAS, photo backup, torrents, AI and a web dashboard on any Android 10+ phone. No root, one free APK.
Translation pending
Developer toolsKotlin · HTMLGitHub @sudiptacmdOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Turn an old Android phone into a home server: SSH, web hosting, Jellyfin with hardware transcoding, Immich, qBittorrent, Home Assistant and a web dashboard. No root.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Homedroid: turn your old Android phone into a home server
That phone in your drawer has an 8-core CPU, a hardware video encoder, gigabytes of RAM, Wi-Fi,
a battery that works as a built-in UPS, and it sips about as much power as an LED bulb.
Homedroid turns it into a self-hosted home server: a media server, a NAS for your USB drive
or microSD card, a photo backup, a torrent box, a web host, a network-wide ad blocker, a private AI and a smart-home hub,
all managed from a beautiful web dashboard. No root, no Termux, no Linux skills needed:
one APK.Read the full README ↗That phone in your drawer has an 8-core CPU, a hardware video encoder, gigabytes of RAM, Wi-Fi, a battery that works as a built-in UPS, and it sips about as much power as an LED bulb. Homedroid turns it into a self-hosted home server: a media server, a NAS for your USB drive or microSD card, a photo backup, a torrent box, a web host, a network-wide ad blocker, a private AI and a smart-home hub, all managed from a beautiful web dashboard. No root, no Termux, no Linux skills needed: one APK.
A 4½-minute tour: the phone app, the dashboard, files on an SD card, a Linux shell in the browser, Jellyfin, AI on the phone, a deployment from GitHub and two phones working as one. Watch the full video (MP4, 13 MB) or see the clips on the website.
Homedroid is in beta (0.9.4) and free. Get the APK from the latest release:
| Your phone | APK |
|---|---|
| Almost every phone from the last ~8 years (64-bit) | homedroid-<version>-arm64-v8a.apk |
| Older 32-bit phones | homedroid-<version>-armeabi-v7a.apk |
| Emulators, Chromebooks | homedroid-<version>-x86_64.apk |
| Not sure | homedroid-<version>-universal.apk (larger, runs everywhere) |
Open it on the phone and allow installing from your browser or file manager, or run
adb install homedroid-<version>-arm64-v8a.apk. SHA256SUMS in each release lets you check
the download. Every release is signed with the same key, so newer versions install over older
ones and keep your data.
Updating: in the dashboard open Settings → App updates → Download and install. The phone fetches the right APK itself and Android asks you to confirm (on Android 12 and later, updates after the first install without a prompt). The server comes back on the new version by itself.
| Module | What it does | Port |
|---|---|---|
| Dashboard | Manage everything from any browser: live load, files, a terminal, apps, logs, updates | 8800 |
| SSH & SFTP | A real shell and file transfer, public-key login, ssh -L port forwarding | 8022 |
| Web hosting | Caddy serving ~/www, plus sites deployed from GitHub | 8080 |
| Cloudflare Tunnel | Publish services on the internet without port forwarding; works behind CGNAT | – |
| Tailscale | Reach every module privately from your own devices, anywhere; optional HTTPS, exit node and subnet routes. No root, no VPN slot | – |
| Jellyfin | Media server with hardware transcoding on the phone's video encoder | 8096 |
| Immich | Google Photos-style backup of your photos and videos | 2283 |
| qBittorrent | Torrents download in order into the media library and show up in Jellyfin; torrent <magnet> over SSH | 8081 |
| Home Assistant | Home automation (network and cloud integrations) | 8123 |
| IPCam | Live view from any camera, photos, video, motion recording, the microphone, and spoken announcements | 8800 |
| AI | Chat and an OpenAI-compatible API, with models on the phone (llama.cpp, GPU or CPU) or cloud services; scheduled routines that brief you by voice or push | 8090 |
| AdGuard Home | Network-wide ad and tracker blocking, with blocklists, statistics and a query log; tailnet devices can use the phone as their DNS server | 3000 (DNS 1053) |
| Network tools | This phone's network, device discovery, ping, traceroute, DNS lookups, port scans, HTTP checks, LAN and internet speed tests | 8800 |
| Wake on LAN | Wake computers at home from anywhere the dashboard reaches; also from scripts and phone shortcuts |
http://192.168.1.20:8800,
and Dashboard login shows its password.~/.ssh/id_ed25519.pub) under Settings → SSH public keys.ssh -p 8022 <phone-ip> # a shell; `alpine` opens a root shell in Alpine Linux
sftp -P 8022 <phone-ip> # files; put your website in www/
ssh -p 8022 <phone-ip> torrent 'magnet:?…' # download, in order, into Media/Downloads
ssh -p 8022 <phone-ip> torrent list # progress
Reach it from anywhere. Turn on the Tailscale module and log in with the link it shows:
every module is then at http://homedroid:<port> on your tailnet. Or publish a service on the
internet with a Cloudflare Tunnel: create a tunnel in Cloudflare Zero Trust, point a public
hostname at http://localhost:<port> and paste the tunnel token. Both modules have a
step-by-step Setup guide in the dashboard.
Open http://<phone-ip>:8800 on a computer or a phone. It works without internet access:
everything it needs is inside the app.
Plug a USB drive into the phone (with an OTG adapter) or insert a microSD card, and it shows up
in Files next to the phone's own storage. Stream a video straight from it, drop a folder of
photos onto it, or download a whole folder as a zip. Over SFTP the drive is at
/storage/<volume-id>, so sshfs, rclone, WinSCP, Cyberduck or your file manager's
sftp:// support can mount it like a network drive.
Apps can live there too: choose Storage location on an app and pick the card. On Android
11+ the data goes to a visible Homedroid/<name> folder (this needs All files access, which
the app asks for). Databases and settings always stay in internal storage, because cards and
drives are usually FAT32 or exFAT. If the card is removed, apps wait for it instead of writing
somewhere else, and the location simply disappears from Files until it's back. With storage
access every volume also appears inside Alpine at /storage/…, so existing folders can be added
to Jellyfin.
Good to know: FAT32 can't hold files over 4 GB (use exFAT for movies), and on Android 10 apps can only write to their own folder on removable storage, which Android deletes if Homedroid is uninstalled. The storage picker tells you when that applies.
Jellyfin and qBittorrent share one Media folder. Installing both adds a Downloads library to Jellyfin that picks up finished torrents within a minute, and torrents download in order so you can start watching before they finish. Jellyfin transcodes with the phone's video encoder, so the CPU stays cool (see how).
Give a repository URL, branch and port. Node.js, Python and static sites are detected; build
and start commands and environment variables can be set. Apps get $PORT; static sites are
served by Caddy. Redeploy pulls and rebuilds, and a failed build keeps the old version
running. Auto-deploy checks the branch every 5 minutes, so no public webhook is needed. For
private repositories use https://<token>@github.com/you/repo with a read-only token; it is
never shown back in the dashboard.
Turn on AI in Modules, then open the AI page:
.gguf link, or
upload one. The page recommends the best model your phone runs well, from its memory, free
space and measured speed. Nothing leaves the phone.http://<phone-ip>:8090/v1 is an OpenAI-compatible API with its own
key. Use local/<model> or <service>/<model> as the model name.Expect a few to a dozen words a second with small (0.5–3B) models. llama.cpp needs a 64-bit phone.
Turn on IPCam, then tap Enable camera access in the phone app once. From the dashboard you can watch a live view from any camera, take photos, record video, toggle the torch, listen to or record the microphone, and Announce a typed message with the phone's voice.
Install AdGuard Home from Modules and it filters ads and trackers out of DNS lookups for every device you point at it, with blocklists, statistics and a query log in its web UI (port 3000; the login is on the app card).
Android won't let an app use port 53, the port every device asks for DNS on, so AdGuard Home listens on 1053. The easiest way round that is Tailscale: Homedroid's Tailscale passes port 53 on the phone's tailnet address to 1053, so in the Tailscale admin console you add the phone as a custom nameserver, turn on Override DNS servers, and every device on your tailnet is filtered, at home or on mobile data. Routers that can forward DNS to a port (OpenWrt, pfSense, anything with dnsmasq) can send the whole home network there too. The Setup guide on the card walks through both.
Turn on Network tools and a Network page shows this phone's addresses, gateway, DNS servers and live throughput, and finds the devices around it (mDNS, SSDP/UPnP and quick TCP probes of the local network). Its tools run on the phone and stream their output to the page: ping, traceroute, DNS lookups (handy for testing AdGuard Home), port scans with nmap, HTTP checks with timings and certificate expiry, whois, LAN speed tests with iperf3 (the phone can be the server too) and an internet speed test. Live packet capture needs root, so there's no Wireshark capture; everything else works as an app.
Turn on Wake on LAN, add your computers with their MAC addresses, and wake them with one
click from anywhere the dashboard reaches: over Tailscale, through a Cloudflare tunnel, or from
another phone in your cluster. Homedroid sends the magic packet on every network the phone is
on, and shows whether a computer is on when you give it an address. Scripts and phone shortcuts
can call POST /api/wol/wake with the dashboard password as a Bearer token.
Got more than one old phone? Open Cluster → Add a phone: phones on the same network show up by themselves (or enter an address, for example over Tailscale). Both phones show the same 6-digit code; approve it on the phone being added.
Phones talk over mutual TLS on port 8801 and trust only phones they were paired with. A phone in the cluster can fully control the others, so give each one a strong dashboard password.
Add Homedroid status from your launcher's widgets: every phone in the cluster (online, battery, temperature) and the services running on this one, refreshed every minute.
Android was never meant to run servers, and apps don't get root. Here's how Homedroid gets a full Linux server stack running inside an ordinary app, without asking you to root anything.
Since Android 10, apps may only run programs that shipped inside their APK, from the folder
where Android unpacks native libraries. So Homedroid's own servers (SSH, Caddy, cloudflared,
Tailscale, FFmpeg) are compiled for Android and packed into the APK disguised as libraries
(libsshd.so, libcaddy.so and so on). Android unpacks them, and Homedroid runs them from there.
Everything else runs in Alpine Linux under proot. proot
intercepts a program's system calls and translates them, so software that expects a normal Linux
machine (paths like /usr/bin, a root user, /proc) believes it has one, while really it is
living inside the app's private folder. Alpine (3 MB, checksum-verified) is downloaded on first
use and unpacked by Homedroid's own small extractor, because Android 10's tar gives up on
ownership changes apps aren't allowed to make.
Docker needs kernel features no Android app can use. But a container image is really just a
stack of tarballs plus a recipe. Oci.kt talks to
Docker Hub and ghcr.io directly: it picks your phone's architecture, downloads each layer,
verifies its checksum, and stacks them the way Docker would, including deleted-file markers.
Android forbids hard links in app storage, so those become copies, and absolute symlinks are
rewritten to stay inside the image. The result runs under proot like any other Alpine program.
That's how Immich runs from its official images on a phone, PostgreSQL and all.
Every phone has a dedicated video encoder that can squeeze 1080p video in real time while barely warming up. On a PC, Jellyfin reaches such hardware through Linux interfaces (VAAPI, V4L2). On Android the encoder is only reachable through MediaCodec, which lives in Android's own libraries and talks to system services, so a normal Linux FFmpeg can't use it.
So Homedroid builds a separate FFmpeg for Android with MediaCodec support and plays a small
trick on Jellyfin: it turns on Jellyfin's V4L2 hardware option and points Jellyfin at a tiny shim.
When Jellyfin asks for h264_v4l2m2m, the shim quietly runs the Android FFmpeg with
h264_mediacodec instead; everything else goes to Jellyfin's own FFmpeg. To let that Android
binary run inside Alpine, Android's /system, /apex and /vendor are mounted into the
Alpine world. Jellyfin never knows it's on a phone.
Some upstream code needed help with Android's sandbox. native/patches/ keeps the changes,
each one small and explained:
| Patch | Why |
|---|---|
proot-fork-to-clone | Android rejects fork/vfork on x86_64 and armv7, and musl uses them; rewrite them as clone(SIGCHLD). |
proot-sigsys-fork | The same rewrite for syscalls the sandbox traps with SIGSYS, so proot's fast seccomp mode works everywhere (≈50× faster for syscall-heavy work). |
proot-sigsys-syscall-number | On x86, proot's SIGSYS emulation read the syscall number from a register it had already overwritten, breaking rename(2) and with it uv. |
proot-sysvipc-memfd | Apps targeting Android 10+ can't open /dev/ashmem; back emulated System V shared memory (PostgreSQL) with memfd. |
proot-netlink-reads | Android lets apps read rtnetlink but not write it; proot took that as "netlink blocked" and faked empty replies, hiding every IPv4 address. |
proot-android-default-routes | Android keeps default routes in per-network policy tables, so Linux software thought it was offline (libtorrent: no tracker announces, no DHT). Report them in the main table. |
proot-android-siocgifname | Android denies SIOCGIFNAME to apps, breaking if_indextoname(3); answer it from the tracer. |
ffmpeg-mediacodec-extradata-without-eos | MP4/HLS headers were probed with a dummy frame plus end-of-stream; some encoders stay at EOS afterwards. |
ffmpeg-mediacodec-parameter-sets | Codec2 encoders report SPS/PPS in the output format; capture them and repeat them before every key frame. |
ffmpeg-mediacodec-sync-frames | Honour forced key frames (HLS segment boundaries) with request-sync, and start with a key frame after the probe. |
The supervisor starts every process in its own session and signals whole process groups: proot ignores SIGTERM, and killing only proot would orphan the program it runs.
Measured with tools/bench.py, which drives a phone over adb and SSH. Run it
on your phone and share the output
to add your phone to this table.
| Emulator (Android 10, x86_64, 3 GB RAM) | |
|---|---|
| Idle RAM, SSH + web only | 136 MB (3 processes) |
| Idle RAM, + Jellyfin, qBittorrent, Immich | 1.19 GB (34 processes; Immich ≈ 800 MB) |
| Idle CPU, SSH + web only | 0.1 % of one core |
| Idle CPU, + Jellyfin, qBittorrent, Immich | 2.6 % of one core |
| Network, phone → computer | 457 MB/s HTTP, 194 MB/s SSH (virtual NIC) |
| Transcoding, 1080p → 720p H.264 | 114 fps (3.8× real time) through MediaCodec¹ |
¹ The emulator's MediaCodec encoder is software running on the host. On a phone MediaCodec uses the dedicated video hardware, which is the point: it keeps the CPU free and cool.
Homedroid does a lot inside an ordinary app, and a few things follow from that:
ssh -L.apk upgrade over SSH or by reinstalling from the Apps screen.localhost. Homedroid binds databases to localhost and
protects them with generated passwords. qBittorrent skips authentication for localhost so the
torrent command works.Needs JDK 17+, Go 1.23+ and the Android SDK with NDK r27+ (ANDROID_NDK_HOME).
native/build-all.sh # sshd, Caddy, cloudflared, proot, FFmpeg for arm64, armv7, x86_64
./gradlew assembleRelease # APKs in app/build/outputs/apk/release/
adb install app/build/outputs/apk/release/app-arm64-v8a-release.apk
The individual scripts take ABIs as arguments (native/build-ffmpeg.sh arm64-v8a), and
versions can be overridden (CADDY_VERSION=… CLOUDFLARED_VERSION=… native/build.sh). To sign
release builds with your own key, copy keystore.properties.example to keystore.properties;
without it they use the debug key. Releases are built by CI: pushing a tag such as
v0.9.4-beta builds every ABI, signs the APKs and publishes them with SHA256SUMS.
Apps are data: add an entry to apps.json. A simple app runs
an install script and a command in Alpine (see Jellyfin). A multi-service app pulls container
images and runs several processes, with {{secret}} passwords and @data/@storage binds (see
Immich). See CONTRIBUTING.md.
Homedroid is built in the open, and the most useful thing you can do is try it on your phone and tell us how it went:
tools/bench.py numbers fill the measurements table.apps.json, fixes, translations, docs. Start with
CONTRIBUTING.md.Homedroid is free software under the GNU General Public License v3.0. It bundles and builds third-party components under their own licenses; see THIRD_PARTY.md. The walkthrough uses Blender Foundation open movies (Sintel, Big Buck Bunny, Elephants Dream, Tears of Steel), © Blender Foundation, CC BY.
| 8800 |
| Cluster | Several phones, one dashboard: pick which phone runs each app, move apps with their data, every camera on one page | 8801 |