在 Termux 里把服务一个接一个跑起来之后,最痛苦的事情其实不是安装配置,而是"我到底开了哪些端口""这个服务管理地址是多少来着"。习惯了记在备忘录里,时间一长自己都看懵了。后来我花了一个下午写了张服务导航页,一次性把 Termux 里跑着的下载服务、Web 服务、API 接口都汇总到一个网页上,手机浏览器一开就是全部入口。今天就把这个方案完整拆一遍,适合那些在 Termux 或者类 Linux 终端环境里部署了不少服务、想用最低成本做个统一入口的折腾型玩家。
1. 在动手之前:先想清楚导航页到底要解决什么问题
1.1 我实际遇到过什么样的"服务混乱"
我在主力安卓手机上装了 Termux 很长时间了,里面跑的东西从最初的 open-ssh 和 Python 脚本,慢慢变成了一个微型服务器:有下载服务、有定时任务、有内网测试接口、偶尔还会临时起一个 Python HTTP Server 传文件。一段时间后,问题就变得很现实:打开浏览器想访问某个服务,却要先去 Termux 里敲命令查 IP、查端口、查路径;有些服务是临时启动的,今天用这个端口明天用那个端口,记性稍微差一点就全靠猜。
后来我还遇到过更尴尬的情况:一个临时起的服务没有关掉,端口被后面的服务占用了,排查半天才发现是之前留下的进程。这种事情本质上不是 Termux 不稳定,而是缺少一个"统一视角"。所以我想的不是装一个重型监控面板,而是做一个纯静态、可自动更新的服务导航页——它只干一件事:把端口、IP、服务名、状态入口整理到一个页面上。
1.2 导航页方案的取舍:为什么不是重型管理面板
有人可能会问,既然 Termux 能装 Nginx 也能跑 PHP 和数据库,为什么不直接用一个现成的导航项目,比如 Heimdall 或者 Dashy?我当时确实考虑过,也装过测试。结论是:在 Termux 这种手机上,追求功能丰富往往意味着资源占用高、依赖复杂、更新频率跟不上 Termux 的软件源节奏。
导航页需求其实很轻:能显示服务名、描述、访问链接,最好有分组和在线状态,这就够了。再往上的功能,比如用户登录、统计、仪表盘,在没有公网 IP 的场景下基本用不上。所以我最终选择的是"静态 HTML + 轻量 Web 服务器 + 自动生成脚本"的组合。更新导航页就是更新时间戳然后重新生成一遍 HTML,服务器只需要负责把文件吐出去,任何性能压力都不存在。
1.3 这套方案最终能覆盖哪些应用场景
把方案想明白后,它的适用范围比想象中广:
- 内网多服务入口:手机连着家里的 Wi-Fi,导航页绑定局域网 IP,随手一点就能进服务。
- 临时服务登记:Termux 里临时起的 HTTP 服务、API 调试服务,跑起来的同时顺手写进导航配置,下次打开就知道端口在哪。
- 设备间互访:平板、电脑、另一台手机可以通过浏览器访问导航页,不用每台设备都装终端。
- 快速分享:给需要访问某服务的人发一个局域网链接,不用教对方怎么进 Termux 看端口。
它解决的核心问题是"记忆负担"和"查找成本"。与其说它是一个部署项目,不如说它是一个使用习惯的固化。
2. 环境准备:Termux 该装什么、怎么规划目录
2.1 基础环境初始化
开始之前必须确认 Termux 基础环境是干净的。很多导航页装不成功,罪魁祸首其实是包管理器源没更新或者包冲突。初始化建议按顺序执行:
pkg update pkg upgrade -y termux-setup-storagepkg upgrade 会更新已安装的所有包,首次执行可能耗时较长。这一步不能跳过,因为 Termux 的软件源更新比较频繁,某些依赖包旧版本和新版本差别很大,尤其像 nginx、python 这类核心组件。
termux-setup-storage 是申请存储权限的命令,会创建一个~/storage目录,软链到手机的公共存储空间。导航页的静态文件一般不需要放到公共存储,但我建议把 HTML 文件或脚本备份到公共目录一份,方便用文件管理器查看和编辑。授权以后,即使 Termux 被系统回收,备份文件也还在。
2.2 安装 Web 服务器
导航页需要一个 Web 服务器来对外提供访问。二选一的话,我推荐 Nginx,原因主要有三个:
- Nginx 配置简单,一个 server 块就能搞定端口、根目录和 index。
- 它支持 IPv6 和局域网访问很稳定。
- Termux 官方源里一直有 nginx 的稳定版本,安装和卸载都干净。
安装命令:
pkg install nginx -y如果只是想临时用一下、不想维护进程,那 Python 自带的 http.server 也可以,但只适合临时演示,不适合长期服务:
python -m http.server 8080导航页这种场景我强烈建议走 Nginx,因为后面要加开机自启动、设默认首页、做端口转发,Nginx 的体系更成熟。
2.3 目录规划:别把导航页和站点根目录混在一起
我自己踩过一个坑:一开始把导航页 HTML 放在了~/frontend目录,Nginx 的 root 直接指向那里,结果后续想加子页面、换主题、做版本备份时,目录结构变得很乱。后来我重新规划了一套目录,结构清晰很多:
~/nav-site/ # 导航页项目根目录 ├── public/ # 静态文件,Nginx 直接指向这里 │ └── index.html ├── config/ # 服务清单配置文件,纯文本或 JSON └── scripts/ # 自动生成 HTML 的脚本public是唯一的对外目录,config和scripts对用户不可见。这样做好处很明显:改配置不需要动 Nginx 配置,改 HTML 模板不会干扰服务清单数据,备份整个目录也方便。
2.4 必须记住的几个 Termux 文件路径特性
Termux 的目录结构和常规 Linux 略有不同。常规服务器的/var/www/html、/etc/nginx这些路径,在 Termux 里是不存在的,也不建议自己强行建立。Nginx 安装后的配置文件路径是$PREFIX/etc/nginx/nginx.conf,这里$PREFIX默认是/data/data/com.termux/files/usr,记不住不要紧,直接用$PREFIX变量就行。
另外,Termux 不支持 systemd,所以服务的启停方式跟服务器不一样。Nginx 装完后,手动启动用nginx,停止用nginx -s stop,重载配置用nginx -s reload。这个点后文会自动启动部分再展开。
3. 服务导航页的完整部署过程
3.1 先用最简单的 HTML 跑通访问链路
我不建议一开始就上脚本自动化。第一步,先手动做一个静态 HTML,确认"浏览器通过手机 IP 能访问到页面"这条链路是通的。
在~/nav-site/public/index.html里写一个最基础版本:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Termux 服务导航</title> <style> body { font-family: sans-serif; background: #f4f4f9; margin: 0; padding: 20px; } .card { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.1); } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; } a { text-decoration: none; color: #1a73e8; font-weight: 500; } .group-title { margin: 24px 0 8px; font-size: 18px; } </style> </head> <body> <h1>局域网服务导航</h1> <div class="grid"> <div class="card"><h3>文件服务</h3><p>本地静态文件服务</p><a href="http://{%IP%}:8070">打开</a></div> <div class="card"><h3>API 测试</h3><p>临时 API 接口</p><a href="http://{%IP%}:8080">打开</a></div> </div> </body> </html>{%IP%}是手动占位符,等下要用脚本替换成手机当前 IP。
然后配置 Nginx 的 server 块,让访问 8096 端口时指向这个页面。之所以用 8096,是因为避开常见的 8080、8000、8888,它们很容易被其他服务占用。
3.2 Nginx 关键配置:server 块和路径权限
Nginx 主配置在$PREFIX/etc/nginx/nginx.conf。运行vim或nano打开后,在http块内增加一个 server 配置。我的完整配置如下:
server { listen 8096; root ~/nav-site/public; index index.html; location / { try_files $uri $uri/ =404; } }有两点要特别注意。
第一,root不要用绝对路径/data/data/com.termux/files/usr/...硬编码,虽然 Nginx 能识别,但一旦 Termux 重装或迁移,路径会失效。~符号在 Nginx 配置里有特殊含义,所以最好写明确路径:
root /data/data/com.termux/files/user/nav-site/public;等等,files下的目录名是home,不是user。正确的绝对路径是:
/data/data/com.termux/files/home/nav-site/public如果不想记这么长路径,可以在 nginx.conf 开头定义一个变量,但我不建议新手这样折腾。直接用绝对路径最省心。
第二,权限问题。Termux 里 Nginx 默认以当前用户运行,所以文件权限不要随意 chmod 成 777。只要目录可进入、文件可读即可。我一般用chmod 755 directory && chmod 644 file。
配置完成后测试并重载:
nginx -t nginx -s reload然后用浏览器访问http://127.0.0.1:8096,能在手机上看到导航页,说明第一步通了。
3.3 用脚本把服务清单变成导航页
手动 HTML 的问题在于:每加一个服务都要改一次 HTML。干脆写一个 Shell 脚本,从配置文件读取服务列表,自动生成 HTML。这里我选择用最简单的 C-style shell 加 sed 模板替换,不依赖 Python 之外的运行时——其实 Python 也在 Termux 里常用,所以二选一都行。
我最终用了 Python 脚本,理由是:处理 JSON、HTML 字符串拼接更直观,转义问题少。
配置文件~/nav-site/config/services.json示例:
{ "groups": [ { "name": "下载与传输", "services": [ {"name": "aria2 RPC", "desc": "下载任务管理", "port": 6800, "path": "/web"} ] }, { "name": "开发测试", "services": [ {"name": "API Mock", "desc": "接口模拟服务", "port": 8080, "path": "/docs"} ] } ] }然后写一个 Python 脚本~/nav-site/scripts/build_nav.py,读 JSON,生成 HTML。核心逻辑大致如下:
#!/data/data/com.termux/files/usr/bin/python import json, socket, datetime, os BASE_DIR = os.path.expanduser("~/nav-site") CONFIG = os.path.join(BASE_DIR, "config/services.json") OUTPUT = os.path.join(BASE_DIR, "public/index.html") def get_lan_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] except Exception: ip = "127.0.0.1" finally: s.close() return ip def build_html(data, ip): cards = "" for group in data["groups"]: cards += f'<div class="group-title">{group["name"]}</div>\n<div class="grid">\n' for svc in group["services"]: url = f"http://{ip}:{svc['port']}{svc.get('path', '')}" cards += f'<div class="card"><h3>{svc["name"]}</h3><p>{svc.get("desc", "")}</p><a href="{url}" target="_blank">打开</a></div>\n' cards += '</div>\n' return cards if __name__ == "__main__": with open(CONFIG, "r", encoding="utf-8") as f: data = json.load(f) ip = get_lan_ip() html = f"""<!DOCTYPE html>...{build_html(data, ip)}...""" with open(OUTPUT, "w", encoding="utf-8") as f: f.write(html) print(f"[{datetime.datetime.now()}] generated -> {OUTPUT} IP: {ip}")生成后直接覆盖index.html,然后不需要重载 Nginx,因为 Nginx 直接读文件系统,静态文件变更即时生效。
这个脚本的最大价值:以后新增服务和端口,只需要改 JSON,然后跑一次脚本,导航页就更新了。如果你愿意,还可以把这个命令绑定到 Termux 快捷键或者自动任务里。
3.4 自动探测端口是否在线:让导航页带上状态
单纯的链接在服务挂掉之后还显示在线,会误导访问者。我给脚本加了一个轻量探活:用 socket 去连每个服务的 IP+端口,能连通就打上"在线"标记,连不上就显示"离线",这样打开导航页就能知道服务状态,不用点进去才发现报错。
探活要注意超时不能太长,我设置为 0.5 秒,避免因为某个服务无响应导致整个生成脚本卡死。
def check_port(ip, port, timeout=0.5): import socket as _s s = _s.socket(_s.AF_INET, _s.SOCK_STREAM) s.settimeout(timeout) try: s.connect((ip, port)) s.close() return True except Exception: return False状态显示我做成"● 在线"和"○ 离线"两个样式,视觉上容易分辨。需要注意:探活只是 TCP 层级的连通性检测,能说明端口在监听,但不代表服务功能完全正常,所以状态信息定位为"参考值"就好,别用来做关键监控。
4. 访问、自启动与联动:让导航页真正好用起来
4.1 局域网访问:手机 IP 和端口要配对
Termux 跑在手机上,要访问导航页,最关键的是知道手机在局域网里的 IP 地址。我之前用上面的脚本来自动获取,这里再单独拆解一下获取 IP 的方式。
虽然不是所有网络都允许 UDP 连接,但用 socket 连 8.8.8.8 的方式在大多数安卓手机上都能返回真实内网 IP。还有一种备选方案:
ip -4 addr show wlan0 | grep inet | awk '{print $2}' | cut -d/ -f1这个命令需要确保 Termux 有网络权限。组合使用更稳:优先用termux-wifi-connectioninfo(需要安装 termux-api),它会返回比 socket 猜测更准确的 Wi-Fi 信息。
浏览器访问地址就是http://手机IP:8096。如果是电脑访问,确保电脑和手机在同一个 Wi-Fi 下,不用额外设置防火墙——Termux 里的 Nginx 默认监听所有网卡,不像桌面 Linux 有复杂的 firewalld/ufw 干预。
4.2 开机自启动:用 Termux-services 管理 Nginx
Termux 没有 systemd,但官方提供termux-services工具来管理用户级服务。具体流程:
pkg install termux-services -y mkdir -p $PREFIX/var/service然后为 Nginx 建立一个服务目录。Termux-services 使用的 runit 管理方式,会按目录里的 run 脚本启动进程。我先创建目录:
mkdir -p ~/.termux/service/nginx不过更标准的方式是使用$PREFIX/var/service/nginx。网上很多教程不再赘述,我直接给出可行的方案:
mkdir -p $PREFIX/var/service/nginx cat > $PREFIX/var/service/nginx/run <<'EOF' #!/data/data/com.termux/files/usr/bin/sh exec nginx -g 'daemon off;' EOF chmod +x $PREFIX/var/service/nginx/runnginx -g 'daemon off;'表示以前台模式运行,让 runit 能持续监控进程。如果 Nginx 退出,runit 会自动拉起。
重启 Termux 或者手机后,Nginx 会自动启动。这个机制非常稳,我实测过多次。但要提醒一句:termux-services的自动启动是否生效,取决于 Termux 进程是否被杀。国产安卓系统杀后台严重,建议在系统设置里把 Termux 的自启动、电池白名单都打开。
4.3 定时刷新导航页:让状态信息保持新鲜
服务状态和 IP 都可能变化,所以导航页最好能定期更新。Termux 自带cronie可以用,但我不建议频繁跑探活脚本,因为每次探活会建立大量 TCP 连接,影响耗电和网速。折中方案:每 10 分钟更新一次 IP 和状态。如果 IP 没变化,只更新状态;如果 IP 变了,重新生成整页。
安装 cronie:
pkg install cronie -y sv-enable crond然后写一个 cron 任务:
*/10 * * * * /data/data/com.termux/files/usr/bin/python ~/nav-site/scripts/build_nav.py >> ~/nav-site/logs/generate.log 2>&1注意 cronie 的 PATH 可能很短,脚本里尽量写绝对路径,或者脚本开头显式设置 PATH:
export PATH=/data/data/com.termux/files/usr/bin:$PATH这样即使 cron 环境变量有问题,也能调起 Python。
4.4 安全加固的几点建议
Termux 服务默认没有登录校验,如果有人在你所在的局域网里扫端口扫到 8096,那就直接看到了导航页。我的处理方式:
- 导航页只显示服务名称和链接,不显示敏感端口含义或密码。
- 对某些服务,在 Nginx 层加 basic auth。
- 不把导航页直接暴露到公网,如需外网访问,用 SSH 隧道转发到本地端口。
在 8096 端口上增加 basic auth 的配置:
location / { auth_basic "Nas Nav"; auth_basic_user_file /data/data/com.termux/files/home/nav-site/config/htpasswd; ... }htpasswd 文件生成需要安装apache2-utils里的工具,但 Termux 源里不一定有,可以直接用 Python 生成哈希。不过 basic auth 在局域网访问场景下有点重,很多用户不设。我的建议是最少给导航页加一个访问密码,防止邻居或者公共 Wi-Fi 下其他人误入。
5. 踩坑实录与排查技巧
5.1 Nginx 启动失败,提示"address already in use"
这是最常遇到的问题。通常原因是有其他进程占用了 8096 端口,或者上一次 Nginx 没有正常退出。排查方式:
pkg install nmap nmap 127.0.0.1 -p 8096如果发现端口被占用,用lsof -i :8096查看进程,或者直接杀掉旧进程:
pkill -f "nginx"然后重新启动。更稳妥的做法是在配置里换一个不常用的端口,避开 8080、8090 这类热门段。
5.2 浏览器访问导航页显示 403 Forbidden
403 大部分原因是权限问题,不是配置问题。检查两步:
- 目录权限:
~/nav-site/public和上级目录是否可执行权限(755 即可)。 - Nginx worker 进程的用户是否有权限读取 public 下文件。
Termux 里 Nginx 以当前用户运行,所以一般不是用户问题,更多出现在目录路径打错或者配置文件里 root 指向错误。用一个命令快速验证:
ls -ld ~/nav-site ~/nav-site/public chmod -R o+rX ~/nav-siteo+rX很实用,X 只给目录加执行权限,不改变文件的执行位。
5.3 手机息屏后服务就断了
安卓系统会杀后台进程,Termux 如果在前台运行还没事,一旦切到后台超过几分钟,Nginx 就可能被杀。这个问题不能完全靠 Termux 内部解决,但有几个缓解手段:
- 锁屏时保持 Wi-Fi 连接。
- 在系统设置中允许 Termux 后台活动、关闭电池优化。
- 使用 Termux 的
termux-wake-lock来持有唤醒锁:
pkg install termux-api -y termux-wake-lock执行后 Termux 会一直持有 wakelock,理论上不是太老的安卓系统都能有效防止杀进程。缺点是比较耗电,所以我只在需要长期跑服务的时候开启。
也可以termux-wake-unlock释放,配合 cron 或 Termux:Boot 在特定时段开关。
5.4 IP 变了之后导航页还显示旧 IP
监听变更的推荐方案是扫描网卡并生成,而我遇到过一个问题:脚本通过8.8.8.8连接获取 IP,但当手机网络切换到 5G 热点或者其他不联网的 AP 时,这个 socket 连接可能失败,返回 127.0.0.1。这种情况下导航页就废了。
我有两个兜底方案:
- 优先读
termux-wifi-connectioninfo的 JSON 结果,拿不到再走 socket 探测。 - 如果拿不到局域网 IP,干脆不生成链接,只显示服务列表,同时标注"未检测到网络"。
这样即使网络环境变化,页面也不会给出一堆错误链接。
5.5 端口冲突多,管理混乱
后装的服务可能默认监听 8000、8080 等端口,跟导航页没关系,但会导致混乱。我的经验是给所有服务分配固定端口段:
- 8000-8099:Web 类服务
- 6800:aria2 RPC
- 5000:开发调试 API
- 9273:监控面板
把这些端口登记到services.json的 desc 字段里,生成导航页时自动带上。时间长了你会发现,端口规划其实比页面样式重要得多。
5.6 附:快速排障命令清单
| 症状 | 先试命令 | 再考虑的方向 |
|---|---|---|
| 无法访问导航页 | nginx -t | 检查监听端口和防火墙 |
| 页面显示离线 | python ~/nav-site/scripts/build_nav.py | 看日志确定探活失败原因 |
| 服务时好时坏 | sv status $PREFIX/var/service/nginx | 检查 runit 状态,看日志 |
| 想换端口 | 修改 nginx.conf 并 reload | 同步改 services.json |
最后再分享一点个人经验
我在做这套导航页之前,一直觉得把服务入口做成静态页面有点原始。但实际用下来,反而觉得这种"笨办法"最耐用。它不依赖数据库,不怕 Python 版本升级,不要求额外维护面板服务,顶多就是几个 HTML 和 JSON 文件。如果你在 Termux 里跑的服务数量超过五个,真的值得花一小时搭一个这样的导航页。后续想扩展也不难,比如给页面加上关键词搜索、按标签筛选,或者把服务在线状态用颜色小圆点标出来,这些都是纯前端就能搞定的事。我的建议是先把最基础的跑通,再根据自己使用习惯一点一点加上去,这样维护起来不累,页面也不会越改越复杂。