在 Android 上部署 Linux

2023-03-27 • 更新于 2024-02-20

在 Android 上部署 Linux……好吧,我知道 Android 本身就是个 Linux。不要在意这些细节。

最近把旧手机当时钟用,发现它还有 TF 卡槽,想起何不在上面跑个 Linux,化身低功耗服务器呢,再搭个 Syncthing 私人云盘,岂不美哉(摊手)?

为啥不直接用 Syncthing APP?呃,你不觉得这样很酷吗?我觉得太酷了……

为了物尽其用,又买了个 TF 卡……

长话短说,就是用 Linux Deploy,它能以 chroot 方式在 Android 上创建 Linux 环境。和 Termux 相比,它更像一个完整的 Linux。

其实很早就用过 LP,不过 LP 看起来有段时间没更新了,一大堆 issue 没人理。新手要踩的坑还是很多的,特此记录。

安装

首先,用 Magisk 把手机 root 了。Android 版本太老的话是没法使用新版 Magisk APP 的,在 release 页找老版 Magisk。

其次,把 VPN 之类的关掉(装完再关也可以),那玩意会在手机上建 NAT,局域网设备就没法通过 IP 地址访问了。鬼知道我在这个奇葩问题上花了多少时间……

如果要爬梯,可以用这个 V2Ray Magisk 模块,它能在手机建立透明代理。虽然版本很老,也有一些 bug,但确实能用。

打开 LP,右下角选项,发行版就用 Debian。并不是所有发行版都能用,既然 LP 默认 Debian,意味着它的问题可能是最少的。目前 Debian 最新只能用 buster。

架构就按自己手机来。

官方的源经常访问故障,建议使用国内镜像服务,比如清华源 http://mirrors.tuna.tsinghua.edu.cn/debian/,注意是 http 而不是 https

安装类型,我是直接把环境安装到 TF 卡,所以选择分区,而不是镜像文件或目录。这样不仅保持 Linux 环境和 Android 相互独立,而且读写性能比镜像文件更好。

具体做法是,先把 TF 卡分区为单个 ext4,当然如果你知道你在做什么,也可以建立其它分区,只要下面的安装路径正确即可。

插入 TF 卡,万恶的 Android 会自动挂载,然后在里面拉屎。先不要管它,在设置里把 TF 卡弹出。

安装路径一般为 /dev/block/mmcblk1p1,根据自己的来,可以使用终端、文件管理器等查看。

初始化系统使用 SysV。chroot 环境是不能用 systemd 的。不建议尝试那些模拟 systemd 的替代品,SysV 和 cron 就可以解决大部分问题。

启用 ssh,不要 GUI。

其它的按自己的喜好来。

安装只要十几分钟就搞定了,如果出现问题,到 LP 设置里把调试打开看看 log。

然后启动就能用 ssh 访问了。

另外,由于 Android 省电机制,关闭屏幕后会非常卡,除非保持在 LP 主界面,或安装禁止关屏的 APP。LP 设置里有保持 Wi-Fi 和 CPU 的选项,但用处不大,至少在高版本 Android 是不管用的。

天坑

/etc/rc.local

虽然有 SysV,但很多软件都转用 systemd了。

这时肯定想到 /etc/rc.local 开机脚本,然而它也缺失了。啊这……

所以我们需要手动建立 /etc/init.d/rc.local

#! /bin/sh
### BEGIN INIT INFO
# Provides:          rc.local
# Required-Start:    $all
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:
# Short-Description: Run /etc/rc.local if it exist
### END INIT INFO

# https://forums.raspberrypi.com/viewtopic.php?t=95101


PATH=/sbin:/usr/sbin:/bin:/usr/bin

. /lib/init/vars.sh
. /lib/lsb/init-functions

do_start() {
        if [ -x /etc/rc.local ]; then
                [ "$VERBOSE" != no ] && log_begin_msg "Running local boot scripts (/etc/rc.local)"
                /etc/rc.local
                ES=$?
                [ "$VERBOSE" != no ] && log_end_msg $ES
                return $ES
        fi
}

case "$1" in
    start)
        do_start
        ;;
    restart|reload|force-reload)
        echo "Error: argument '$1' not supported" >&2
        exit 3
        ;;
    stop)
        ;;
    *)
        echo "Usage: $0 start|stop" >&2
        exit 3
        ;;
esac

以及 /etc/rc.local

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

cp /etc/hostname /proc/sys/kernel/hostname

rm -rf /Android

chmod 666 /dev/fuse

exit 0

给它们加上可执行权限,然后用 sysv-rc-conf 或者其它你喜欢的工具启用。

看看我已经在 rc.local 里加了什么内容:

  • /etc/hostname 中的内容不会被读取,所以这里把它复制一下。
  • Android 开机就会自动挂载 TF 卡并在里面拉屎,所以把屎目录 Android 删掉。而且挂载之后,LP 就没法启动了,这个坑之后再说,如何在开机时取消挂载。
  • /dev/fuse 由于权限限制需要修改一下,否则无法使用 Rclone 等工具。

TNND,这 TM 都是坑啊。

关闭 rsyslog

Android 的 loglevel 开到了 7,那不是一般的多:

# cat /proc/sys/kernel/printk
7	4	1	7

而且手动去改也是没用的,估计是因为全被 logcat 接管了。

LP 启动没多久,/var/log 大小就按 GiB 来计算了,非常坑爹,嫌闪存长寿啊。

也考虑过使用 zram 分区,但自带的 logrotate 似乎有 bug,配置不生效,而且不知道 zram 在 chroot 下能否正常使用,等有空再折腾吧。

最后干脆直接用 sysv-rc-conf 关闭了 rsyslog。

清净了!

开机取消挂载 TF 卡

之前已经把环境安装到 TF 卡了,如果想让 LP 开机启动,就不能让系统挂载 TF 卡。

我研究了一下,开机禁止挂载比较麻烦,除非修改源码,但开机自动挂载后再手动取消挂载(弹出)就容易得多。

于是我写了个 Magisk 开机脚本:

它会弹出所有外置存储,比如 TF 卡、U 盘等。

由于脚本里使用了 sm 工具,需要 Android 6.0 以上才能用。

把脚本放到 /data/adb/service.d/ 下重启,看看是不是一挂载就马上弹出了。如果不成功,把 LOG_ON=false 改为 LOG_ON=true ,查看 /data/local/tmp/unmount.log 排查一下原因。

我自己测试,一般在开机后 20 多秒就会弹出,比 LP 启动快。如果弹出太慢,建议把 LP 启动延迟改大一些。

服务

Syncthing

使用 官方仓库 安装。配置略。

Rclone

参考我的文章:使用 Rclone 挂载网盘

Nginx

Nginx 官方仓库里只有 arm64 和 amd64,32 位的手机就用默认仓库里的老版本吧。

Nginx 一大好处是给搭建反向代理,比如:

location /your-syncthing-path/ {
    proxy_pass https://localhost:8384/;
    ...
}

Transmission、aria2 等下载工具都可以这么干,对外隐藏真实端口,更重要的是可以走 HTTPS。今天在网上用 HTTP 和裸奔有什么区别?

好了,再填一个大坑,给你节省半天时间:

由于 Android 权限限制,必须把 www-data 加入 aid_inet 用户组:

# usermod -aG aid_inet www-data

否则没法使用。所以 Android 真不能当作普通的 Linux 发行版……

certbot

certbot 用 apt 安装即可,版本虽老但够用。用 pip 安装的话会给你装 20 几个依赖,人麻了……apt 虽然也有,但起码可以 autoremove

国内 ISP 屏蔽了普通用户的 80 端口,要申请证书可以参考这篇文章

另外,certbot 是没有 SysV 脚本的,要自动更新可以直接添加到 cron,比如每天更新两次 0 0,12 * * * /usr/bin/certbot -q renew,跟 systemd 脚本是一样的效果。

其它

永久开启无线调试

打开终端或 adb shell,使用 su 获取 root 权限后输入:

# setprop persist.adb.tcp.port 5555

重启以后也会保持开启。

注意切不可在公网上开启!Android 漏洞极多,尤其是老旧版本,非常容易成为肉鸡!

关闭旋转建议按钮

我就好奇什么样的低能玩意设计出了这么个脑瘫功能?

$ adb shell settings put secure show_rotation_suggestions 0

充电控制

据说手机常年通电,电池容易鼓包,有搞硬件的大佬会把电池去掉,让主板直接从 USB 取电,我是不想折腾,总有种因噎废食的感觉,万一断电了呢?硬件损伤且不说,数据丢失就很难受。

其实,一个 ACC Magisk 模块就可以搞定,安装完直接重启,默认配置就够用了。它会让手机电量保持在 70 ~ 75,电量低了就充电,高了就断电,省心得很。

F-Droid 特权扩展

F-Droid 不能自动安装 APP,root 也不行。官方有一个特权扩展 OTA 包,可以通过 recovery 刷入。但这会修改 system 分区,我不喜欢。

Magisk 官方模块仓库(已废弃)倒是有一个模块,但一来版本太老,二来写得不咋地,三来作者是个 GD……真晦气。

于是我大体看了一下 Magisk 官方文档,自己写了个模块:F-Droid 特权扩展,通过 GitHub Actions 自动构建,与 F-Droid 官方保持同步。

需要注意的是,安装模块后 F-Droid 网络访问权限可能会丢失,要重新设置一下。这也是个大坑,因为没有任何有用的提示,你会一直怀疑是自己网络问题,然后浪费几个小时调试。

远程控制

由于是作为服务器用的,远程控制还是很有用的。

我试过很多 VNC 软件,都不靠谱。

最后发现了神器:甲壳虫,它基于 scrcpy,通过 adb 控制手机。结合前面永久开启无线调试,用起来十分方便。

如果有公网 IP 或设置好内网穿透,就可以实现字面意义上的远程控制——只要延迟能忍的话。

注意切不可在公网上开启!Android 漏洞极多,尤其是老旧版本,非常容易成为肉鸡!

遗憾的是它只支持标准的 5555 端口,不能自定义,也不支持路径(反向代理)。

时钟

可能是我孤陋寡闻,Android APP 浩如烟海,我竟然找不到一款简洁好用的待机时钟。

目前用的这个名字就不说了,CPU 占用率高得离谱。

有空自己写一个。

AndroidLinux

本作品根据 署名-非商业性使用-相同方式共享 4.0 国际许可 进行授权。

Homebrew 使用中的一些问题

Transmission 屏蔽迅雷等吸血客户端