Vyline DOCS

故障排查

先判断症状属于 Vyline 本体、持久化、Docker 还是 Android host,再决定下一步。

最初检查

docker compose ps
docker compose logs --tail=200
docker inspect vyline
curl -fsS http://127.0.0.1:3000/healthz
df -hT

如果 healthz 返回 200,Vyline 的 HTTP server 本身正在工作。接下来把问题缩小到一个边界:只有持久化失败、只有 container 无法出网,或只有 LAN 无法进入。

恢复的历史在 reload 后消失

优先检查:/app/data bind mount 与写权限。不要一开始就把问题归因到 Protocol 解密。

docker inspect vyline
docker logs vyline
du -sh data storage

解决方法

  1. docker inspect 的 Mounts 中确认 /app/data/app/storage 指向预期的主机目录,并且 RW=true
  2. 如果是只读 mount、目录不存在,或移动数据后仍引用旧路径,修正 Compose/.env。移动现有数据前先停止 container。
  3. 使用当前 image recreate,让 bind mount 初始化重新执行。
docker compose pull
docker compose up -d --force-recreate
docker compose logs --tail=100

恢复完成后先 reload UI,再 restart container。两次之后历史仍保留,才说明 SQLite 持久化已经成功。

容量显示 0 B

先确认 data / storage 是否 mount 到预期位置,以及主机能否取得该 filesystem 的容量。Backend 会对保存位置执行 statfs,特殊 filesystem 或特殊运行环境可能导致容量获取失败。如果怀疑 image 过旧,执行 docker compose pull 后 recreate container,再用同一保存位置确认。

解决方法

df -hT data storage
docker exec vyline sh -lc 'df -hT /app/data /app/storage'

如果 host 与 container 看到的 filesystem 不同,修正 bind mount path。如果 host 本身对 FUSE 等特殊 filesystem 也无法稳定取得容量,把保存位置移到普通 ext4/xfs/btrfs filesystem 后重新 bind。移动数据前先更新到最新 image,并用同一个 mount 再确认一次。

Raspberry Pi / 小型 arm64 host 极慢

free -hvmstat 1docker stats 检查 memory pressure 与 swap in/out。增加 zram/swap 可以避免 OOM,但 restore 或同步仍可能因为 I/O wait 变得极慢。环境可用时再配合 iostat 等检查存储延迟。

解决方法

Android: overlay2 EINVAL

通过 docker info 与 mount 信息确认 /var/lib/docker 所在 filesystem。如果在 F2FS/casefold 等组合下创建 overlay2 确实返回 EINVAL,可以考虑把 Docker data-root 放到 ext4 loop image 作为规避方案。

解决方法

只有确认 F2FS/casefold 是原因时,才在 Android root shell 中为 Docker data-root 创建 ext4 loop。image 大小请按设备剩余空间调整。

ROOT=/data/data/com.termux/files/usr/var/lib/proot-distro/containers/ubuntu/rootfs
mkdir -p /data/local/docker-storage "$ROOT/var/lib/docker"
truncate -s 16G /data/local/docker-storage/docker-ext4.img
/system/bin/mke2fs -t ext4 -F /data/local/docker-storage/docker-ext4.img
LOOP=$(/system/bin/losetup -f)
/system/bin/losetup "$LOOP" /data/local/docker-storage/docker-ext4.img
mount -t ext4 -o rw,noatime "$LOOP" "$ROOT/var/lib/docker"

在 chroot 内重新启动 dockerd,检查 docker info 的 Backing Filesystem,并执行 docker run --rm hello-world。原 filesystem 上 overlay2 正常时不需要这个规避方案。

Android: runc remount / invalid argument

确认 chroot rootfs 已按要求进行 self-bind mount,并设置为 rslave。

解决方法

PREFIX=/data/data/com.termux/files/usr
ROOT="$PREFIX/var/lib/proot-distro/containers/ubuntu/rootfs"
BB="$PREFIX/bin/busybox"

su
$BB mount --bind "$ROOT" "$ROOT"
$BB mount --make-rslave "$ROOT"
$BB mount --rbind /dev "$ROOT/dev"
$BB mount --make-rslave "$ROOT/dev"
$BB mount -t proc proc "$ROOT/proc"
$BB mount --rbind /sys "$ROOT/sys"
$BB mount --make-rslave "$ROOT/sys"
$BB mount -t tmpfs -o mode=755,nosuid,nodev tmpfs "$ROOT/run"

完成这些 mount 后再进入 chroot,重启 dockerd,并确认 docker run --rm hello-world。如果每次 reboot 后都会复发,把这段 mount 顺序放到 dockerd 之前的自动启动流程。

Android: container 无法访问 Internet

检查 ip rule、main route、FORWARD、MASQUERADE,以及 Android netd policy routing。

解决方法

先启用 IPv4 forwarding。只有 packet counter 证明真实 Android 路径经过 legacy iptables 时,才新增专用 chain;不要 flush netd 管理的 chain。

echo 1 > /proc/sys/net/ipv4/ip_forward
WAN_IF=$(ip route get 1.1.1.1 | awk '/dev/ {for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}')
DOCKER_SUBNET=$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Subnet}}')

iptables -N VYDOCKER_FWD 2>/dev/null || true
iptables -t nat -N VYDOCKER_POST 2>/dev/null || true
iptables -C FORWARD -j VYDOCKER_FWD 2>/dev/null || iptables -I FORWARD 1 -j VYDOCKER_FWD
iptables -t nat -C POSTROUTING -j VYDOCKER_POST 2>/dev/null || iptables -t nat -I POSTROUTING 1 -j VYDOCKER_POST
iptables -A VYDOCKER_FWD -i docker0 -o "$WAN_IF" -s "$DOCKER_SUBNET" -j ACCEPT
iptables -A VYDOCKER_FWD -i "$WAN_IF" -o docker0 -d "$DOCKER_SUBNET" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -A VYDOCKER_POST -s "$DOCKER_SUBNET" -o "$WAN_IF" -j MASQUERADE

如果 Android policy routing 不会落到 main table,先查看现有 ip rule,选择不冲突的 priority,只为 Docker subnet 添加 lookup main rule。不要删除 netd 现有规则。最后执行 docker run --rm alpine ping -c 1 1.1.1.1,再验证 DNS/HTTPS。完整 packet path 见 Android 网络

Android: LAN 访问 -p 3000:3000 时 reset

依次确认 host 是否在 listen、Docker 创建的 NAT rule、Android 实际使用的 iptables backend,以及 packet 真正经过的 chain。在已测试设备上出现过 nft 与 legacy rule path 不一致导致该症状的情况,但在确认目标设备实际 packet path 之前,不要直接追加固定 DNAT rule。

解决方法

如果 host 上 curl http://127.0.0.1:3000/healthz 正常,并且 counter 已确认走 legacy path,再用真实 LAN interface、container IP 和 bridge 名创建专用 DNAT/FORWARD rule。

CID=$(docker ps -qf 'name=vyline')
CONTAINER_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CID")
NETWORK_ID=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' "$CID")
BRIDGE_IF="br-$(printf '%s' "$NETWORK_ID" | cut -c1-12)"
LAN_IF=wlan0  # 用 ip -br addr 的实际值替换

iptables -t nat -N VYDOCKER_PRE 2>/dev/null || true
iptables -N VYDOCKER_FWD 2>/dev/null || true
iptables -t nat -C PREROUTING -j VYDOCKER_PRE 2>/dev/null || iptables -t nat -I PREROUTING 1 -j VYDOCKER_PRE
iptables -C FORWARD -j VYDOCKER_FWD 2>/dev/null || iptables -I FORWARD 1 -j VYDOCKER_FWD
iptables -t nat -A VYDOCKER_PRE -i "$LAN_IF" -p tcp --dport 3000 -j DNAT --to-destination "$CONTAINER_IP:3000"
iptables -A VYDOCKER_FWD -i "$LAN_IF" -o "$BRIDGE_IF" -p tcp -d "$CONTAINER_IP" --dport 3000 -j ACCEPT

Compose recreate 后 container IP 与 bridge 名可能变化。需要此规避方案的设备,应在 boot 或 Docker network 变化时只重建这些专用 chain。非 Wi-Fi 场景不要固定使用 wlan0

Android: docker exec 看不到 /app

PID=$(docker inspect -f '{{.State.Pid}}' vyline)
readlink /proc/$PID/ns/mnt
ls -la /proc/$PID/root/app
nsenter -t "$PID" -m -- ls -la /app
docker exec vyline ls -la /app

如果 container PID root 正常,问题可能仅存在于 docker exec 使用的 namespace。

解决方法

如果 /proc/$PID/rootnsenter 都正常,只有 docker exec 异常,不要删除 image 或 volume。按上面的 self-bind + rslave 方式重新建立 real-chroot mount,在该 mount namespace 中重启 dockerd,再 recreate container。

如果应用 HTTP 正常,只有 Compose healthcheck 通过同一 exec path 失败,在确认原因后可以禁用 healthcheck 作为规避。排查时用 nsenter 查看 container 的真实 mount view。

远程访问返回 401 / 403

非 loopback bind 即使 VYLINE_LAN_ACCESS=false 也需要 remote authentication。检查 subdevice pairing 状态与 installation ID。只有在 Cloudflare Access、Tailscale ACL 等已经对访问路径本身完成认证时,才为 remote browser 的 owner-only 操作设置 VYLINE_TRUST_REMOTE_OWNER=true

解决方法

  1. 普通 LAN 使用时保持 VYLINE_TRUST_REMOTE_OWNER=false,从 loopback owner UI 把远程浏览器重新 pair 为 subdevice。
  2. 如果清除了 browser data,或换了 browser/profile,installation ID 会变化,需要重新 pairing。
  3. 只有 Cloudflare Access、Tailscale ACL、已认证 reverse proxy 等在请求到达 Backend 之前已经限制用户,并且确实需要 remote owner 操作时,才设置 VYLINE_TRUST_REMOTE_OWNER=true 并 recreate container。
docker compose up -d --force-recreate
docker compose logs --tail=100

在未经额外认证的 LAN/Internet 暴露上直接设置 VYLINE_TRUST_REMOTE_OWNER=true 不是解决方案。先建立认证边界。

按页面、设置或命令搜索