Transforma um celular Android rooteado num servidor Linux de verdade: Debian com systemd, subindo sozinho no boot, independente de qualquer aplicativo.
Não é proot, não é Termux, não é emulação. É um chroot real sobre uma imagem ext4, dentro de namespaces de mount e PID, com o systemd como PID 1 — iniciado pelo Magisk antes de qualquer aplicativo carregar.
Validado em: Samsung Galaxy S20+ (SM-G985F), LineageOS 23.2, Android 16. A arquitetura não tem nada de específico desse aparelho: funciona em qualquer Android com Magisk.
- Por que não Termux
- Arquitetura
- Instalação
- Operação diária
- Docker (opcional)
- Ajustes para uso 24/7
- Solução de problemas
- Estrutura
Os scripts populares de "Linux no Termux" têm três amarras:
Emulação. O proot intercepta e emula chamadas de sistema. Funciona, mas é lento e nem tudo se comporta como deveria.
Ciclo de vida. Tudo roda como processo filho do aplicativo. Fechou o Termux, o Android matou o processo, acabou o servidor.
Localização do rootfs. Fica em /data/data/com.termux/..., que pertence ao
UID do aplicativo, é credential-encrypted (inacessível no boot antes do
primeiro desbloqueio) e some se você limpar os dados do app.
Aqui os processos são nativos, iniciados pelo init do sistema via Magisk,
com o rootfs em armazenamento device-encrypted. Nada disso depende de app.
Android (init do sistema)
└─ Magisk service.d → linux-start.sh
└─ imagem ext4 montada com suid,dev
└─ unshare -m -p (namespaces de mount e PID)
└─ chroot
└─ systemd (PID 1 lá dentro)
├─ ssh.service
├─ cron.service
└─ o que mais você quiser
Visto do Android, é um processo comum. Visto de dentro, é um sistema Linux completo com PID 1 próprio.
Imagem ext4, não pasta. O /data do Android é montado nosuid,nodev. Com
o rootfs numa pasta, sudo e ping quebram sem mensagem clara. A imagem é
montada com suid,dev e resolve. De quebra, backup vira copiar um arquivo.
Imagem esparsa. 32 GB declarados ocupam só o que for usado de fato.
unshare -m -p antes do chroot. O namespace de PID é o que permite ao
systemd ser PID 1. O de mount faz todos os binds desaparecerem sozinhos quando
o container morre — nada vaza para o Android.
oom_score_adj = -1000. Sem isso o lmkd do Android mata o servidor
quando a memória aperta.
Wakelock de kernel. echo linux_server > /sys/power/wake_lock impede a CPU
de suspender. Sem isso o servidor some da rede em segundos de inatividade.
Regra de SELinux para o driver loop. Quem lê o arquivo de imagem é uma
thread do kernel no domínio u:r:kernel:s0, que não tem acesso aos rótulos de
/data. Sem uma regra pontual via magiskpolicy, o mount falha com Invalid argument e o dmesg mostra avc: denied. Não é preciso setenforce 0. A
regra é volátil, por isso o linux-start.sh a reaplica a cada boot.
Verificação de identidade do processo. O init.pid sobrevive ao reboot e o
Android reaproveita PIDs baixos rapidamente. Checar só kill -0 já fez o
script concluir que o container estava vivo quando o PID gravado havia virado
com.android.packageinstaller. Agora os scripts confirmam que a raiz do
processo é a nossa imagem.
- Android com Magisk (root) e depuração USB ativada
- platform-tools (adb) no computador
- ~2 GB livres no
/datapara começar
Debian 13 (trixie) — usado na validação:
https://raw.githubusercontent.com/debuerreotype/docker-debian-artifacts/dist-arm64v8/trixie/oci/blobs/rootfs.tar.gz
Ubuntu Base 24.04 — alternativa: https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/
Deixe o arquivo na pasta host/.
No Windows, dentro de host/:
.\deploy.batO .bat chama o PowerShell com -ExecutionPolicy Bypass só nessa execução,
sem alterar a configuração do sistema. Ele localiza o adb, confere o
dispositivo e envia tudo — inclusive o rootfs, se estiver na pasta.
Em Linux ou macOS, basta adb push dos arquivos de device/ para
/sdcard/Download/.
adb shellsu
sh /sdcard/Download/prepare.sh
O Magisk vai pedir permissão na tela do celular. Se a tela estiver bloqueada, o comando fica pendurado. Desbloqueie e toque em Permitir.
O prepare.sh instala os scripts em /data/linux/, corrige quebra de linha
CRLF (que causa erros not found incompreensíveis) e imprime os próximos
passos.
Todo o resto acontece dentro do aparelho. Comandos longos com aspas
passados via adb shell são reinterpretados pelo shell do Android e quebram de
formas difíceis de depurar.
Primeiro em simulação — não altera nada:
/data/local/tmp/cleanup-old.sh
Ele varre /data atrás de rootfs antigos (até 14 níveis de profundidade — o
chroot-distro do Termux enterra o dele fundo), lista caminho, tamanho e
distro de cada um, e procura processos rodando dentro deles. Um chroot é só
um processo do Android com outra raiz: apagar os arquivos não mata o processo,
e enquanto ele viver vai segurar portas.
Conferindo que a lista está correta:
/data/local/tmp/cleanup-old.sh --apply
/data/linux/install-rootfs.sh /sdcard/Download/rootfs.tar.gz
Padrão de 32 GB. Para mudar: SIZE_GB=64 /data/linux/install-rootfs.sh ...
/data/linux/run-setup.sh
Instala systemd, SSH, sudo, cron e utilitários; mascara as unidades que não
fazem sentido num container (getty, udevd, modules-load); ajusta fuso e
locale; cria o usuário server; e configura o journal como volátil, para não
desgastar a memória flash do aparelho à toa.
Não define senhas. Isso fica por sua conta, no passo seguinte.
/data/linux/linux-start.sh
/data/linux/linux-shell.sh
Dentro do Debian:
passwd server
exitDe outra máquina da rede:
ssh server@<ip-do-celular>O linux-status.sh mostra IP, portas ouvindo, estado do wakelock e da bateria.
Só depois que o SSH funcionar:
cp /data/linux/99-linux.sh /data/adb/service.d/99-linux.sh
chmod 755 /data/adb/service.d/99-linux.sh
Reinicie sem abrir o Termux e confirme que o SSH responde. Esse é o teste que prova a independência.
O 99-linux.sh também deixa um watchdog: se o container cair, reergue em até
60 s. Uma parada intencional pelo linux-stop.sh é respeitada — ele cria uma
marca que o watchdog verifica.
| Ação | Comando (root no Android) |
|---|---|
| Diagnóstico completo | /data/linux/linux-status.sh |
| Iniciar | /data/linux/linux-start.sh |
| Parar | /data/linux/linux-stop.sh |
| Shell | /data/linux/linux-shell.sh |
| Log de boot | cat /data/linux/boot.log |
| Backup | parar e copiar /data/linux/rootfs.img |
O linux-shell.sh detecta se o container está vivo: se estiver, entra nele por
nsenter; se não, abre um chroot temporário de setup.
Para execução não interativa:
SHELL_CMD='/bin/bash /root/meu-script.sh' /data/linux/linux-shell.shRequer um kernel recompilado. O kernel que as ROMs distribuem vem com
IPC_NS, POSIX_MQUEUE, CGROUP_DEVICE e BRIDGE desativados, e sem eles o
dockerd não sobe — não há flag que contorne.
Para descobrir se o seu precisa:
adb push docker/docker-check.sh /data/local/tmp/
adb shell su -c 'sh /data/local/tmp/docker-check.sh'Para Exynos 990 (Galaxy S20 / Note 20), a receita de recompilação está em exynos990-docker-kernel. Para outros aparelhos o princípio é o mesmo, mudando a árvore do kernel.
Com o kernel pronto, docker/DOCKER.md cobre instalação, uso e limitações.
Resumo: funciona, com --network=host no lugar de -p porta:porta.
Celular plugado em 100% o dia inteiro incha a bateria em poucos meses. Veja
Configurações > Bateria > Controle de carregamento do LineageOS. Se não
existir, use o módulo Magisk ACC (Advanced Charging Controller):
acc -s capacity 40 60 60adb shell settings put global wifi_sleep_policy 2
adb shell settings put global adaptive_battery_management_enabled 0E reserve um IP fixo no DHCP do roteador.
Para rede cabeada, um hub USB-C com PD passthrough permite carregar e usar Ethernet ao mesmo tempo — muda o patamar de estabilidade.
Sem ventoinha o aparelho throttla. Tire a capa. Se necessário, prenda os
processos pesados nos núcleos pequenos com taskset -c 0-3.
/mnt/android dentro do Linux aponta para /data/media/0 — acesso direto ao
armazenamento interno, sem passar pelo FUSE. Duas ressalvas: é
credential-encrypted, então só fica legível depois do primeiro desbloqueio
após um reboot; e arquivos criados por ali não aparecem na galeria até um
rescan. Para dados de serviço, use a própria imagem ext4 ou um HD USB em ext4.
not found ao rodar qualquer script
Quebra de linha CRLF. sed -i 's/\r$//' /data/linux/*.sh
mount ... failed: Invalid argument
SELinux bloqueando o driver loop. Confirme no dmesg:
avc: denied { read } ... scontext=u:r:kernel:s0. O fix-sepolicy.sh resolve
com uma regra pontual via magiskpolicy, reaplicada a cada boot pelo
linux-start.sh.
Serviço sobe mas não responde na rede
Falta o grupo aid_inet (GID 3003). O kernel do Android tem paranoid
network: sem esse grupo suplementar, um usuário não-root não abre socket — e
falha sem mensagem. usermod -aG aid_inet <usuario>
sudo: effective uid is not 0
A imagem montou nosuid. Confirme com linux-status.sh.
systemd em degraded
Quase sempre getty tentando abrir um console inexistente. systemctl --failed mostra. O setup-debian.sh já mascara.
Address already in use na porta 22
Processo de uma instalação anterior ainda vivo. Descubra com
readlink /proc/<pid>/root e mate. O cleanup-old.sh faz isso.
Container não sobe após reboot Pidfile obsoleto com PID reaproveitado. Os scripts atuais verificam a identidade do processo pela raiz; se estiver com versão antiga, atualize.
host/ executados no computador
deploy.ps1 / deploy.bat envia tudo por adb
prepare.sh instala os scripts no aparelho
device/ executados no aparelho, como root
install-rootfs.sh cria a imagem ext4 e extrai o rootfs
migrate-rootfs.sh migra um chroot existente para a imagem
cleanup-old.sh remove instalações anteriores (simulação por padrão)
run-setup.sh dispara o setup-debian.sh dentro do chroot
setup-debian.sh configura o Debian (roda dentro do chroot)
linux-start.sh sobe o container
linux-stop.sh desliga e desmonta
linux-shell.sh shell dentro do container
linux-status.sh diagnóstico
fix-sepolicy.sh regra de SELinux para o driver loop
99-linux.sh autostart + watchdog (vai em /data/adb/service.d/)
docker/ Docker dentro do container (requer kernel recompilado)
diag/ scripts de diagnóstico pontual