在 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 langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleTermux 服务导航/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 classgrid div classcardh3文件服务/h3p本地静态文件服务/pa hrefhttp://{%IP%}:8070打开/a/div div classcardh3API 测试/h3p临时 API 接口/pa hrefhttp://{%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 fdiv classgroup-title{group[name]}/div\ndiv classgrid\n for svc in group[services]: url fhttp://{ip}:{svc[port]}{svc.get(path, )} cards fdiv classcardh3{svc[name]}/h3p{svc.get(desc, )}/pa href{url} target_blank打开/a/div\n cards /div\n return cards if __name__ __main__: with open(CONFIG, r, encodingutf-8) as f: data json.load(f) ip get_lan_ip() html f!DOCTYPE html...{build_html(data, ip)}... with open(OUTPUT, w, encodingutf-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, timeout0.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 管理 NginxTermux 没有 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 变了重新生成整页。安装 croniepkg 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 21注意 cronie 的 PATH 可能很短脚本里尽量写绝对路径或者脚本开头显式设置 PATHexport 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 Forbidden403 大部分原因是权限问题不是配置问题。检查两步目录权限~/nav-site/public和上级目录是否可执行权限755 即可。Nginx worker 进程的用户是否有权限读取 public 下文件。Termux 里 Nginx 以当前用户运行所以一般不是用户问题更多出现在目录路径打错或者配置文件里 root 指向错误。用一个命令快速验证ls -ld ~/nav-site ~/nav-site/public chmod -R orX ~/nav-siteorX很实用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-8099Web 类服务6800aria2 RPC5000开发调试 API9273监控面板把这些端口登记到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 里跑的服务数量超过五个真的值得花一小时搭一个这样的导航页。后续想扩展也不难比如给页面加上关键词搜索、按标签筛选或者把服务在线状态用颜色小圆点标出来这些都是纯前端就能搞定的事。我的建议是先把最基础的跑通再根据自己使用习惯一点一点加上去这样维护起来不累页面也不会越改越复杂。