From b78da6159abf07b03550a58dfdf69397b59b3a64 Mon Sep 17 00:00:00 2001 From: Freecode100Year Date: Fri, 13 Nov 2026 04:36:17 -0500 Subject: [PATCH 1/3] docs: rewrite bilingual README Based on the rewrite in #2, updated for what changed since: the current host fingerprint (the one in #2 was the old server's), the chat.nya.je web entrance, no kicks for quiet sessions or locked phones, offline install, and Android in one line (details in docs/android.md). Co-Authored-By: Claude Opus 5.5 --- README.en.md | 90 +++++++++++++++++++++------------------------------- README.md | 90 +++++++++++++++++++++------------------------------- 2 files changed, 74 insertions(+), 106 deletions(-) diff --git a/README.en.md b/README.en.md index 1fcb08d..c82ec0d 100644 --- a/README.en.md +++ b/README.en.md @@ -1,89 +1,73 @@ -# VIVANT · Anonymous terminal chat room +# VIVANT · Anonymous terminal chat [简体中文](README.md) | English [![CI](https://github.com/Freecode100Year/vivant/actions/workflows/ci.yml/badge.svg)](https://github.com/Freecode100Year/vivant/actions/workflows/ci.yml) -One SSH command and you're in the chat room. No sign-up, no password, no app. +Join an open chat room over SSH. No account, password, or app required. -``` +```sh ssh -p 2222 guest@term.freedom8964.com ``` +On your first connection, check that the SSH host fingerprint matches: + +```text +SHA256:wBGFVRp15FZKX625k5Bh2VjGXwOqCqtAydpP0+fPyQA +``` + +No terminal? Open [https://chat.nya.je/](https://chat.nya.je/) to join the same chat in your browser. + ``` VIVANT Alice · 在线 Online 3 · 运行 Up 4d 12h · 18:34 CST 房间 ROOMS ▸#general 3 #chinese 0 +2 #english 0 #technology 0 #anonymous 0 [18:32] 大家晚上好! [18:33] Hello from New York! -[18:33] 有人在学编程吗?@Alice [18:34] [私信 DM ← Ghost] hi ── #general · 3 人在线 here · 大厅 · Lobby · 什么都可以聊 ───────────────────────── > 说点什么… Type a message… /help 帮助 help -Enter 发送 send · ↑↓ 历史 history · PgUp/PgDn 滚动 scroll · Ctrl+C 退出 quit ● 已连接 CONNECTED ``` -Every prompt, the help and the key hints are shown in both Simplified Chinese and English. - -## How to connect - -**Web:** open [https://chat.nya.je/](https://chat.nya.je/) to join the chat in your browser. - -**Computer** (Windows, macOS and Linux all ship ssh): run the command above. On first connect SSH asks whether to trust the server; type `yes`. - -**iPhone / iPad**: +## Inside the chat -- **iSH Shell**: free on the App Store. Run `apk add openssh-client` once, then the command above. -- **Termius**: add a host with address `term.freedom8964.com`, port `2222`, username `guest`, password empty. +- Type to talk. Use `/rooms` to see channels and `/join english` to switch rooms. +- Send a private message with `/msg `, or change your nickname with `/nick `. +- Menus, prompts, and help are shown in both Simplified Chinese and English. Type `/help` for commands. +- Choose an eye-friendly green, black, or white terminal theme. If your terminal has display issues, use the `plain` username for text mode. +- Nobody is disconnected for staying quiet or for locking their phone. +- AI agents can connect through SSH in JSON mode; see [AI agents](docs/agents.en.md). -If your network only lets port 443 through, use `-p 443` instead of `-p 2222` (the operator has to enable it). Any username works and there is no password. If the screen is too narrow or shows garbage, use the username `plain` for line mode. More ways to connect (Tor, scripts, skipping the fingerprint prompt): [Connecting and using](docs/usage.en.md). To connect an AI agent: [AI agents](docs/agents.en.md). - -## Everyday commands - -Just type to talk. - -``` -/rooms list rooms -/join english join a room -/msg private message -/nick change your nickname -/who who is here -/help all every command -``` +On a computer (Windows, macOS and Linux include ssh), run the command above. For iPhone and iPad setup, Tor, and script usage, see [Connecting and using](docs/usage.en.md). If your network only allows TCP 443, you can use `-p 443` only when the operator has enabled the alternate port. ## Privacy -- No accounts; your SSH public key is never sent to the server; no IPs, nicknames or messages in the logs; messages live in memory only and expire. -- **The server can read the chat.** Don't post anything that identifies you. In the chat, `/privacy` shows what this server keeps and doesn't, and `/forget` erases your messages. -- To hide your IP from the server too: connect over Tor (if the operator enabled it). +- No account is needed, and the server does not receive ordinary users' SSH public keys. +- **The server can read chat messages. SSH is not end-to-end encryption.** Don't share information that identifies you. +- By default, room history stays in memory only, up to 500 messages per room for up to 24 hours; a server restart clears it. Operators can change these settings. Private messages are not stored. Use `/privacy` to see this server's settings and `/forget` to remove your messages from room history. +- The program does not log IP addresses, nicknames, or chat messages. Connecting over Tor hides your real IP from the server; connecting through an `.onion` address requires Tor to be enabled by the operator. -Details: [Security and anonymity](docs/security.en.md). +See [Security and anonymity](docs/security.en.md) for more about the limits and implementation. -## Run your own +## Host your own -On a Linux VPS: +Install on a Linux VPS with one command: -```bash +```sh curl -fsSL https://raw.githubusercontent.com/Freecode100Year/vivant/main/install.sh | sudo bash ``` -Manage it with `sudo vivant-ctl status | upgrade | uninstall`. If people can't connect, run `sudo vivant-ctl doctor`: it checks everything and tells you how to fix it. Remember to allow TCP 2222 in your cloud provider's firewall, and on Cloudflare set the DNS record to DNS only (grey cloud). Docker works too, see [Deployment and configuration](docs/deploy.en.md#docker-optional). - -**Server can't reach GitHub:** download the offline one-click installer from the release page and run `bash vivant--linux-offline.sh --to root@SERVER` on your computer, see [Offline install](docs/deploy.en.md#offline-install-server-cant-reach-github). For a temporary server on Windows / macOS (testing, LAN), the release page has those binaries too, see [Deployment and configuration](docs/deploy.en.md#windows--macos). - -**An Android phone as the server:** install `vivant-android-arm64.apk`, tap "Start", and people on the same Wi-Fi or the phone's hotspot can join; EasyTier or FRP lets people elsewhere in too, see [Android phone as a server](docs/android.en.md). - -**Upgrading from Private-Talk Terminal:** VIVANT used to be called Private-Talk Terminal (`ptterm`). Run the command above once on the old server and it migrates automatically, keeping the host key, configuration, bans and onion address. See [Deployment and configuration](docs/deploy.en.md#migrating-from-private-talk-terminal). +Manage it with `sudo vivant-ctl status | doctor | upgrade | uninstall`. If the server can't reach GitHub, use the offline installer from the releases page; Docker and temporary Windows / macOS servers are also available. See [Deployment and configuration](docs/deploy.en.md). -A 1-core, 1 GB VPS handles about 2000 people online at once. Installer options, admins and settings: [Deployment and configuration](docs/deploy.en.md). +**An Android phone as the server:** install the APK from the releases page and tap Start; people on the same Wi-Fi or hotspot can join. See [Android phone as a server](docs/android.en.md). ## Documentation -- [Android phone as a server](docs/android.en.md): installing the APK, hotspot / LAN connections, building it -- [Connecting and using](docs/usage.en.md): every platform, iPhone step by step, all commands, Tor -- [AI agents](docs/agents.en.md): `bot@` JSON mode, example program -- [Security and anonymity](docs/security.en.md): what the operator can see and what can't be promised -- [Deployment and configuration](docs/deploy.en.md): installer options, migration, admins, settings, Docker -- [Performance](docs/performance.en.md): load tests and design -- [Development](docs/development.en.md): build, test, release, code layout, roadmap +- [Connecting and using](docs/usage.en.md) +- [AI agents](docs/agents.en.md) +- [Security and anonymity](docs/security.en.md) +- [Deployment and configuration](docs/deploy.en.md) +- [Android phone as a server](docs/android.en.md) +- [Performance and capacity](docs/performance.en.md) +- [Development, build, and release](docs/development.en.md) -Written in Rust, one static binary, no database, MIT licensed. +VIVANT is written in Rust and released under the MIT license. diff --git a/README.md b/README.md index 00d7412..5ef2ad0 100644 --- a/README.md +++ b/README.md @@ -4,86 +4,70 @@ [![CI](https://github.com/Freecode100Year/vivant/actions/workflows/ci.yml/badge.svg)](https://github.com/Freecode100Year/vivant/actions/workflows/ci.yml) -一条 SSH 命令进入聊天室。不用注册、不用密码、不用装 App。 +用 SSH 进入一个开放的聊天室:不用注册、没有密码,也不需要安装 App。 -``` +```sh ssh -p 2222 guest@term.freedom8964.com ``` +第一次连接时,请核对 SSH 显示的主机指纹: + +```text +SHA256:wBGFVRp15FZKX625k5Bh2VjGXwOqCqtAydpP0+fPyQA +``` + +不想用终端?打开 [https://chat.nya.je/](https://chat.nya.je/) 在浏览器里进入同一个聊天室。 + ``` VIVANT Alice · 在线 Online 3 · 运行 Up 4d 12h · 18:34 CST 房间 ROOMS ▸#general 3 #chinese 0 +2 #english 0 #technology 0 #anonymous 0 [18:32] 大家晚上好! [18:33] Hello from New York! -[18:33] 有人在学编程吗?@Alice [18:34] [私信 DM ← Ghost] hi ── #general · 3 人在线 here · 大厅 · Lobby · 什么都可以聊 ───────────────────────── > 说点什么… Type a message… /help 帮助 help -Enter 发送 send · ↑↓ 历史 history · PgUp/PgDn 滚动 scroll · Ctrl+C 退出 quit ● 已连接 CONNECTED ``` -所有提示、帮助和按键说明都同时显示简体中文和英文。 +## 聊天室里有什么 -## 怎么连 +- 直接打字发言;用 `/rooms` 查看频道,`/join chinese` 切换频道。 +- 用 `/msg <昵称> <内容>` 私聊,或用 `/nick <名字>` 改昵称。 +- 所有菜单、提示和帮助都同时显示简体中文与英文;输入 `/help` 查看命令。 +- 终端界面支持护眼绿、黑、白主题;终端不兼容时,用户名换成 `plain` 可用纯文本模式。 +- 不发言挂机、手机锁屏都不会被踢下线。 +- AI 智能体可以通过 SSH 的 JSON 模式接入,见 [AI 智能体接入](docs/agents.md)。 -**网页入口**:打开 [https://chat.nya.je/](https://chat.nya.je/) 即可在浏览器中进入聊天室。 +电脑(Windows / macOS / Linux 自带 ssh)运行上面的命令即可。iPhone / iPad 的 iSH、Termius 等连接步骤,以及 Tor 和脚本用法,见[连接与使用](docs/usage.md)。如果你的网络只放行 TCP 443,只有在部署者开启备用端口后才能改用 `-p 443`。 -**电脑**(Windows / macOS / Linux 自带 ssh):运行上面那条命令。第一次连接会问是否信任服务器,输入 `yes`。 +## 隐私说明 -**iPhone / iPad**: +- 不需要账号;普通用户的 SSH 公钥不会发送给服务器。 +- **服务器能读取聊天内容。SSH 不是端到端加密。** 不要发送能识别你身份的信息。 +- 默认情况下,房间消息记录只在内存里保留(每个房间最多 500 条、最长 24 小时),服务器重启后清空;私聊不保存。部署者可以修改房间记录设置。输入 `/privacy` 查看当前服务器的数据设置,输入 `/forget` 删除自己发过的房间消息。 +- 程序日志不记录 IP、昵称或聊天内容。通过 Tor 连接可以隐藏真实 IP;使用 `.onion` 地址则需要部署者开启 Tor。 -- **iSH Shell**:App Store 免费下载,先运行 `apk add openssh-client`,再运行上面的命令。 -- **Termius**:新建主机,地址 `term.freedom8964.com`,端口 `2222`,用户名 `guest`,密码留空。 +更多边界与实现细节见[安全与匿名性](docs/security.md)。 -网络只放行 443 时,把 `-p 2222` 换成 `-p 443`(需部署者开启)。用户名随便填,没有密码。屏幕太窄或显示乱码时,把用户名换成 `plain`,进入纯文本模式。更多连接方式(Tor、脚本、免确认指纹)见[连接与使用](docs/usage.md);接入 AI 智能体见 [AI 智能体接入](docs/agents.md)。 +## 自建聊天室 -## 常用命令 - -直接打字就是发言。 - -``` -/rooms 有哪些房间 -/join chinese 进入房间 -/msg <昵称> <内容> 私聊 -/nick <名字> 改昵称 -/who 谁在这里 -/help all 全部命令 -``` +在 Linux VPS 上运行一条命令即可安装: -## 隐私 - -- 没有账号;你的 SSH 公钥不会发给服务器;日志里没有 IP、昵称和聊天内容;消息只在内存里,过期自动删除。 -- **服务器能看到聊天内容**,别发能认出你的信息。在聊天室里输入 `/privacy` 可以查看这台服务器存什么、不存什么,`/forget` 删除你发过的消息。 -- 想连 IP 也不让服务器看到:通过 Tor 连接(需要部署者开启)。 - -详细说明见[安全与匿名性](docs/security.md)。 - -## 部署自己的聊天室 - -在一台 Linux VPS 上运行: - -```bash +```sh curl -fsSL https://raw.githubusercontent.com/Freecode100Year/vivant/main/install.sh | sudo bash ``` -装好后用 `sudo vivant-ctl status | upgrade | uninstall` 管理;连不上时运行 `sudo vivant-ctl doctor`,它会逐项检查并告诉你怎么修。别忘了在云厂商的防火墙里放行 TCP 2222;域名在 Cloudflare 上要设为 DNS only(灰云)。也可以用 Docker 部署,见[部署与配置](docs/deploy.md#docker可选)。 - -**服务器连不上 GitHub:** 下载发布页里的离线一键安装包,在自己电脑上运行 `bash vivant-<版本>-linux-offline.sh --to root@服务器` 即可部署,见[离线安装](docs/deploy.md#离线安装服务器连不上-github)。想在 Windows / macOS 上临时跑一个(测试、局域网),发布页也有对应的程序,见[部署与配置](docs/deploy.md#windows--macos)。 - -**用安卓手机当服务器:** 装上 `vivant-android-arm64.apk`,点一下「启动」,同一 Wi-Fi 或手机热点下的人就能连进来;还能用 EasyTier 或 FRP 让外网的人进来,见[安卓手机当服务器](docs/android.md)。 - -**从 Private-Talk Terminal 升级:** VIVANT 以前叫 Private-Talk Terminal(`ptterm`)。在旧服务器上运行一次上面的命令即可自动迁移,主机密钥、配置、封禁、洋葱地址都会保留,详见[部署与配置](docs/deploy.md#从-private-talk-terminal-迁移)。 +装好后用 `sudo vivant-ctl status | doctor | upgrade | uninstall` 管理。服务器连不上 GitHub 时用发布页的离线安装包;也可以用 Docker,或在 Windows / macOS 上临时运行。见[部署与配置](docs/deploy.md)。 -单核 1GB 的 VPS 大约能承载 2000 人同时在线。安装参数、管理员、配置项见[部署与配置](docs/deploy.md)。 +**用安卓手机当服务器:** 装上发布页的 APK,点「启动」,同一 Wi-Fi 或热点下的人就能连进来,见[安卓手机当服务器](docs/android.md)。 ## 文档 -- [连接与使用](docs/usage.md):各平台连接方法、iPhone 详细步骤、全部命令、Tor -- [AI 智能体接入](docs/agents.md):`bot@` JSON 模式、示例程序 -- [安全与匿名性](docs/security.md):部署者能看到什么、不能保证什么 -- [安卓手机当服务器](docs/android.md):APK 安装、热点 / 局域网连接、自己构建 -- [部署与配置](docs/deploy.md):一键安装参数、迁移、管理员、配置项、Docker -- [性能](docs/performance.md):压测数据和设计 -- [开发](docs/development.md):构建、测试、发布、代码结构、路线图 +- [连接与使用](docs/usage.md) +- [AI 智能体接入](docs/agents.md) +- [安全与匿名性](docs/security.md) +- [部署与配置](docs/deploy.md) +- [安卓手机当服务器](docs/android.md) +- [性能与容量](docs/performance.md) +- [开发、构建与发布](docs/development.md) -Rust 编写,单个静态二进制,无数据库,MIT 许可。 +VIVANT 使用 Rust 编写,以 MIT 许可证发布。 From 09dc38240369bced1fdbc4773ba6a73c51f0b036 Mon Sep 17 00:00:00 2001 From: Freecode100Year Date: Fri, 13 Nov 2026 04:46:40 -0500 Subject: [PATCH 2/3] Android: EasyTier only (remove FRP) Internet access for the phone server is now off or EasyTier. The FRP option, its settings, frpc build job and libfrpc.so are gone, so the APK is smaller and the CI no longer builds frp. A phone that had chosen FRP starts with internet access off and can pick EasyTier again. CI checks that libfrpc.so is no longer in the APK. Tested on an Android 16 emulator: the screen shows only Off and EasyTier, EasyTier shows its join commands, and a saved "frp" choice falls back to Off without a crash. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/android.yml | 40 ++---- .github/workflows/release.yml | 2 +- .../freecode100year/vivant/MainActivity.java | 50 +------- .../github/freecode100year/vivant/Prefs.java | 11 +- .../freecode100year/vivant/Tunnels.java | 119 +++++------------- .../freecode100year/vivant/VivantService.java | 7 +- .../app/src/main/res/layout/activity_main.xml | 48 +------ android/app/src/main/res/values/strings.xml | 8 -- android/build-tunnels.sh | 29 ++--- android/tunnels.env | 3 +- docs/android.en.md | 12 +- docs/android.md | 12 +- 12 files changed, 71 insertions(+), 270 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index b07ddb3..2b10936 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,9 +1,9 @@ name: Android -# Builds the Android APKs: the Rust server plus the EasyTier and frp tunnel -# clients, all cross-compiled from source with the NDK, packed into a small -# app that runs them as a foreground service. -# 构建安卓 APK(手机上运行的 VIVANT 服务器 + EasyTier / frp 内网穿透)。 +# Builds the Android APKs: the Rust server plus the EasyTier tunnel client, +# both cross-compiled from source with the NDK, packed into a small app that +# runs them as a foreground service. +# 构建安卓 APK(手机上运行的 VIVANT 服务器 + EasyTier 内网穿透)。 # # Stable signing (so updates install over each other): add the repository # secrets ANDROID_KEYSTORE_BASE64 (base64 of a .jks), ANDROID_KEYSTORE_PASSWORD, @@ -59,34 +59,8 @@ jobs: path: android/app/src/main/jniLibs/${{ matrix.abi }}/libeasytier.so if-no-files-found: error - frpc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Versions - run: grep -E "^[A-Z_]+=" android/tunnels.env >> "$GITHUB_ENV" - - name: Source - run: git clone -q --depth 1 --branch "$FRP_VERSION" https://github.com/fatedier/frp android/.tunnel-src/frp - - uses: actions/setup-go@v6 - with: - go-version-file: android/.tunnel-src/frp/go.mod - cache-dependency-path: android/.tunnel-src/frp/go.sum - - name: Build - run: | - set -o pipefail - android/build-tunnels.sh frpc 2>&1 | tee build.log - file android/app/src/main/jniLibs/*/libfrpc.so - - name: Report failure - if: failure() - run: .github/report-failure.sh build.log - - uses: actions/upload-artifact@v4 - with: - name: frpc - path: android/app/src/main/jniLibs/ - if-no-files-found: error - apk: - needs: [easytier, frpc] + needs: [easytier] runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -126,7 +100,6 @@ jobs: J=android/app/src/main/jniLibs for abi in arm64-v8a armeabi-v7a x86_64; do cp tunnels/easytier-$abi/libeasytier.so $J/$abi/ - cp tunnels/frpc/$abi/libfrpc.so $J/$abi/ done ls -l $J/* @@ -163,12 +136,13 @@ jobs: cp $d/app-arm64-v8a-release.apk out/vivant-android-arm64.apk cp $d/app-armeabi-v7a-release.apk out/vivant-android-armv7.apk cp $d/app-x86_64-release.apk out/vivant-android-x86_64.apk - for f in libvivant.so libeasytier.so libfrpc.so; do + for f in libvivant.so libeasytier.so; do for abi in arm64-v8a armeabi-v7a x86_64; do unzip -l out/vivant-android.apk | grep -q "lib/$abi/$f" || { echo "missing $abi/$f"; exit 1; } done unzip -l out/vivant-android-arm64.apk | grep -q "lib/arm64-v8a/$f" || { echo "arm64 apk missing $f"; exit 1; } done + if unzip -l out/vivant-android.apk | grep -q libfrpc.so; then echo "libfrpc.so should be gone"; exit 1; fi ls -l out - name: Report failure diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab3a37c..8e72397 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -152,7 +152,7 @@ jobs: Windows / macOS:`vivant-windows-*.exe`、`vivant-macos-*` 只适合本机测试或局域网,没有安装器、沙箱和开机自启 · for local testing or a LAN only — no installer, sandbox or autostart. - 安卓手机当服务器(含 EasyTier / FRP 外网访问)· Android phone as a server (with EasyTier / FRP internet access): 大多数手机下载 `vivant-android-arm64.apk`,不确定就下 `vivant-android.apk`(通用版)· most phones: `vivant-android-arm64.apk`; not sure: `vivant-android.apk` (universal)(Android 8+,见 [docs/android.md](https://github.com/Freecode100Year/vivant/blob/main/docs/android.md) · see [docs/android.en.md](https://github.com/Freecode100Year/vivant/blob/main/docs/android.en.md)) + 安卓手机当服务器(含 EasyTier 外网访问)· Android phone as a server (with EasyTier internet access): 大多数手机下载 `vivant-android-arm64.apk`,不确定就下 `vivant-android.apk`(通用版)· most phones: `vivant-android-arm64.apk`; not sure: `vivant-android.apk` (universal)(Android 8+,见 [docs/android.md](https://github.com/Freecode100Year/vivant/blob/main/docs/android.md) · see [docs/android.en.md](https://github.com/Freecode100Year/vivant/blob/main/docs/android.en.md)) 程序 Binaries: `vivant-linux-amd64` (x86_64), `vivant-linux-arm64` (ARM64, e.g. Oracle Ampere), `vivant-linux-armv7`;用 `SHA256SUMS` 校验 · verify with `SHA256SUMS`. (`ptterm-linux-*` are the same files under the old names.) EOF diff --git a/android/app/src/main/java/io/github/freecode100year/vivant/MainActivity.java b/android/app/src/main/java/io/github/freecode100year/vivant/MainActivity.java index 586b33e..ee86143 100644 --- a/android/app/src/main/java/io/github/freecode100year/vivant/MainActivity.java +++ b/android/app/src/main/java/io/github/freecode100year/vivant/MainActivity.java @@ -45,10 +45,10 @@ public class MainActivity extends Activity { private final Handler ui = new Handler(Looper.getMainLooper()); private TextView status, online, tunnelStatus, error, connect, netHint, netConnect, fingerprint, log, advToggle, logToggle; private Button toggle, battery, etNew; - private EditText port, maxClients, rooms, extra, etPeer, etName, etSecret, frpServer, frpServerPort, frpToken, frpRemotePort; + private EditText port, maxClients, rooms, extra, etPeer, etName, etSecret; private CheckBox autostart; private RadioGroup tunnel; - private View etGroup, frpGroup, advGroup; + private View etGroup, advGroup; private ScrollView logScroll; private long shownLog = -1; private int tick; @@ -88,14 +88,9 @@ protected void onCreate(Bundle b) { etPeer = findViewById(R.id.et_peer); etName = findViewById(R.id.et_name); etSecret = findViewById(R.id.et_secret); - frpServer = findViewById(R.id.frp_server); - frpServerPort = findViewById(R.id.frp_server_port); - frpToken = findViewById(R.id.frp_token); - frpRemotePort = findViewById(R.id.frp_remote_port); autostart = findViewById(R.id.autostart); tunnel = findViewById(R.id.tunnel); etGroup = findViewById(R.id.et_group); - frpGroup = findViewById(R.id.frp_group); try { ((TextView) findViewById(R.id.version)).setText("v" + getPackageManager().getPackageInfo(getPackageName(), 0).versionName); } catch (PackageManager.NameNotFoundException ignored) { @@ -155,26 +150,19 @@ private void loadFields() { autostart.setChecked(Prefs.autostart(this)); etName.setText(Prefs.etName(this)); etSecret.setText(Prefs.etSecret(this)); - frpServer.setText(Prefs.frpServer(this)); - frpServerPort.setText(String.valueOf(Prefs.frpServerPort(this))); - frpToken.setText(Prefs.frpToken(this)); - frpRemotePort.setText(String.valueOf(Prefs.frpRemotePort(this))); String t = Prefs.tunnel(this); - tunnel.check(Prefs.TUNNEL_EASYTIER.equals(t) ? R.id.tunnel_et : Prefs.TUNNEL_FRP.equals(t) ? R.id.tunnel_frp : R.id.tunnel_off); + tunnel.check(Prefs.TUNNEL_EASYTIER.equals(t) ? R.id.tunnel_et : R.id.tunnel_off); showTunnelGroups(); } private String tunnelFromRadio() { int id = tunnel.getCheckedRadioButtonId(); - if (id == R.id.tunnel_et) return Prefs.TUNNEL_EASYTIER; - if (id == R.id.tunnel_frp) return Prefs.TUNNEL_FRP; - return Prefs.TUNNEL_OFF; + return id == R.id.tunnel_et ? Prefs.TUNNEL_EASYTIER : Prefs.TUNNEL_OFF; } private void showTunnelGroups() { String t = tunnelFromRadio(); etGroup.setVisibility(Prefs.TUNNEL_EASYTIER.equals(t) ? View.VISIBLE : View.GONE); - frpGroup.setVisibility(Prefs.TUNNEL_FRP.equals(t) ? View.VISIBLE : View.GONE); } @Override @@ -232,22 +220,6 @@ private boolean saveSettings() { ok = false; } } - int fsp = parse(frpServerPort), frp = parse(frpRemotePort); - String server = frpServer.getText().toString().trim(); - if (Prefs.TUNNEL_FRP.equals(t)) { - if (server.isEmpty()) { - frpServer.setError(getString(R.string.err_need)); - ok = false; - } - if (fsp < 1 || fsp > 65535) { - frpServerPort.setError(getString(R.string.err_need)); - ok = false; - } - if (frp < 1 || frp > 65535) { - frpRemotePort.setError(getString(R.string.err_need)); - ok = false; - } - } if (!ok) return false; Prefs.sp(this).edit() .putInt("port", p) @@ -259,10 +231,6 @@ private boolean saveSettings() { .putString("tunnel", t) .putString("et_name", name) .putString("et_secret", secret) - .putString("frp_server", server) - .putInt("frp_server_port", fsp > 0 ? fsp : 7000) - .putString("frp_token", frpToken.getText().toString()) - .putInt("frp_remote_port", frp > 0 ? frp : p) .apply(); return true; } @@ -310,8 +278,7 @@ private void render() { boolean editable = !run && !wanted; for (View v : new View[]{port, maxClients, rooms, extra, etPeer, etName, etSecret, etNew, - frpServer, frpServerPort, frpToken, frpRemotePort, - findViewById(R.id.tunnel_off), findViewById(R.id.tunnel_et), findViewById(R.id.tunnel_frp)}) { + findViewById(R.id.tunnel_off), findViewById(R.id.tunnel_et)}) { v.setEnabled(editable); } @@ -349,13 +316,6 @@ private void renderInternet() { + " --port-forward tcp://127.0.0.1:2222/" + Prefs.ET_IP + ":" + relay + "\n\nssh -p 2222 guest@127.0.0.1"; hint = getString(R.string.net_hint_et) + "\n" + getString(R.string.net_hint_et_app, relay, Prefs.ET_IP); - } else if (Prefs.TUNNEL_FRP.equals(t)) { - String server = frpServer.getText().toString().trim(); - int remote = parse(frpRemotePort); - if (!server.isEmpty() && remote > 0) { - cmd = "ssh -p " + remote + " guest@" + server; - hint = getString(R.string.net_hint_frp); - } } netHint.setVisibility(cmd == null ? View.GONE : View.VISIBLE); netConnect.setVisibility(cmd == null ? View.GONE : View.VISIBLE); diff --git a/android/app/src/main/java/io/github/freecode100year/vivant/Prefs.java b/android/app/src/main/java/io/github/freecode100year/vivant/Prefs.java index 63c967d..349d7d1 100644 --- a/android/app/src/main/java/io/github/freecode100year/vivant/Prefs.java +++ b/android/app/src/main/java/io/github/freecode100year/vivant/Prefs.java @@ -14,7 +14,6 @@ final class Prefs { static final String TUNNEL_OFF = "off"; static final String TUNNEL_EASYTIER = "easytier"; - static final String TUNNEL_FRP = "frp"; /** This phone's address inside the EasyTier network. */ static final String ET_IP = "10.144.144.1"; @@ -40,7 +39,10 @@ static SharedPreferences sp(Context c) { static void setRunning(Context c, boolean on) { sp(c).edit().putBoolean("running", on).apply(); } // internet access - static String tunnel(Context c) { return sp(c).getString("tunnel", TUNNEL_OFF); } + /** off or easytier. FRP was removed; a phone that had chosen it starts with internet access off. */ + static String tunnel(Context c) { + return TUNNEL_EASYTIER.equals(sp(c).getString("tunnel", TUNNEL_OFF)) ? TUNNEL_EASYTIER : TUNNEL_OFF; + } static String etName(Context c) { return generated(c, "et_name", "vivant-", 10); } static String etSecret(Context c) { return generated(c, "et_secret", "", 16); } static String etPeer(Context c) { @@ -55,11 +57,6 @@ static String etMachineId(Context c) { } return id; } - static String frpServer(Context c) { return sp(c).getString("frp_server", "").trim(); } - static int frpServerPort(Context c) { return sp(c).getInt("frp_server_port", 7000); } - static String frpToken(Context c) { return sp(c).getString("frp_token", ""); } - static int frpRemotePort(Context c) { return sp(c).getInt("frp_remote_port", DEFAULT_PORT); } - static String frpProxyName(Context c) { return generated(c, "frp_name", "vivant-", 6); } /** A new random EasyTier network name and password. */ static void regenerateEasyTier(Context c) { diff --git a/android/app/src/main/java/io/github/freecode100year/vivant/Tunnels.java b/android/app/src/main/java/io/github/freecode100year/vivant/Tunnels.java index 08f765f..0502ea4 100644 --- a/android/app/src/main/java/io/github/freecode100year/vivant/Tunnels.java +++ b/android/app/src/main/java/io/github/freecode100year/vivant/Tunnels.java @@ -3,9 +3,6 @@ import android.content.Context; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; @@ -15,73 +12,38 @@ import java.util.regex.Pattern; /** - * Internet access for the phone's chat room through a bundled tunnel: - * EasyTier (P2P mesh, no server of your own; visitors run EasyTier too) or - * frpc (your own frps server; visitors just ssh to it). Both forward to the - * server's local relay listener, so tunnelled users share generous limits + * Internet access for the phone's chat room through the bundled EasyTier + * (P2P mesh, no server of your own; visitors run EasyTier too). It forwards to + * the server's local relay listener, so tunnelled users share generous limits * instead of all counting as one address (127.0.0.1). */ final class Tunnels { private Tunnels() {} - static String binary(Context c, String kind) { - String lib = Prefs.TUNNEL_EASYTIER.equals(kind) ? "libeasytier.so" : "libfrpc.so"; - return new File(c.getApplicationInfo().nativeLibraryDir, lib).getAbsolutePath(); + static String binary(Context c) { + return new File(c.getApplicationInfo().nativeLibraryDir, "libeasytier.so").getAbsolutePath(); } - static List command(Context c, String kind) throws IOException { + static List command(Context c) { List cmd = new ArrayList<>(); - cmd.add(binary(c, kind)); - if (Prefs.TUNNEL_EASYTIER.equals(kind)) { - // no TUN device (that would need Android's VPN permission): other - // nodes reach this phone's loopback ports at ET_IP through EasyTier's - // own network stack - add(cmd, "--no-tun", "-i", Prefs.ET_IP, - "--network-name", Prefs.etName(c), - "--network-secret", Prefs.etSecret(c), - "--hostname", "vivant", - "--machine-id", Prefs.etMachineId(c), - "--bind-device", "false", - "--file-log-level", "off", - "--console-log-level", "info"); - for (String p : Prefs.etPeer(c).split("[,\\s]+")) { - if (!p.isEmpty()) add(cmd, "-p", p); - } - } else { - File conf = new File(c.getFilesDir(), "frpc.toml"); - Files.write(conf.toPath(), frpConfig(c).getBytes(StandardCharsets.UTF_8)); - add(cmd, "-c", conf.getAbsolutePath()); + cmd.add(binary(c)); + // no TUN device (that would need Android's VPN permission): other + // nodes reach this phone's loopback ports at ET_IP through EasyTier's + // own network stack + add(cmd, "--no-tun", "-i", Prefs.ET_IP, + "--network-name", Prefs.etName(c), + "--network-secret", Prefs.etSecret(c), + "--hostname", "vivant", + "--machine-id", Prefs.etMachineId(c), + "--bind-device", "false", + "--file-log-level", "off", + "--console-log-level", "info"); + for (String p : Prefs.etPeer(c).split("[,\\s]+")) { + if (!p.isEmpty()) add(cmd, "-p", p); } return cmd; } - static String frpConfig(Context c) { - StringBuilder t = new StringBuilder(); - t.append("serverAddr = ").append(q(Prefs.frpServer(c))).append('\n'); - t.append("serverPort = ").append(Prefs.frpServerPort(c)).append('\n'); - if (!Prefs.frpToken(c).isEmpty()) t.append("auth.token = ").append(q(Prefs.frpToken(c))).append('\n'); - t.append("loginFailExit = false\n"); - t.append("log.to = \"console\"\nlog.level = \"info\"\nlog.disablePrintColor = true\n"); - t.append("transport.tls.enable = true\n\n"); - t.append("[[proxies]]\n"); - t.append("name = ").append(q(Prefs.frpProxyName(c))).append('\n'); - t.append("type = \"tcp\"\nlocalIP = \"127.0.0.1\"\n"); - t.append("localPort = ").append(Prefs.relayPort(c)).append('\n'); - t.append("remotePort = ").append(Prefs.frpRemotePort(c)).append('\n'); - return t.toString(); - } - - /** TOML basic string. */ - static String q(String s) { - StringBuilder b = new StringBuilder("\""); - for (char ch : s.toCharArray()) { - if (ch == '"' || ch == '\\') b.append('\\').append(ch); - else if (ch < 0x20 || ch == 0x7f) b.append(String.format(Locale.ROOT, "\\u%04x", (int) ch)); - else b.append(ch); - } - return b.append('"').toString(); - } - private static void add(List l, String... a) { for (String s : a) l.add(s); } @@ -90,42 +52,19 @@ private static void add(List l, String... a) { // 2026-11-08T08:34:14.514Z INFO CORE::INSTANCE::CONNECTION: [uuid] new peer added peer_id=1 private static final Pattern ET_LINE = Pattern.compile("^\\d{4}-\\d\\d-\\d\\dT\\S+\\s+(\\w+)\\s+\\S+?:\\s+(?:\\[[0-9a-f-]{36}\\]\\s+)?(.*)$"); - // 2026-11-08 03:39:39.016 [I] [client/control.go:174] [816ebbb0ac97ff72] [vivant-ab] start proxy success - private static final Pattern FRP_LINE = Pattern.compile("^\\S+ \\S+ \\[(\\w)\\] \\[[^\\]]+\\] (?:\\[[0-9a-f]+\\] )?(.*)$"); /** Returns the line to show, or null to hide it (EasyTier's startup config dump). Updates ServerState. */ - static String onLine(String kind, String raw) { - String level, msg; - if (Prefs.TUNNEL_EASYTIER.equals(kind)) { - Matcher m = ET_LINE.matcher(raw); - if (!m.matches()) return null; - level = m.group(1); - msg = m.group(2); - if (msg.contains("new peer added") || msg.contains("new peer connection added")) { - ServerState.tunnel = ServerState.TUNNEL_UP; - ServerState.tunnelError = null; - } - } else { - Matcher m = FRP_LINE.matcher(raw); - if (!m.matches()) return null; - level = m.group(1); - msg = m.group(2); - if (msg.contains("start proxy success")) { - ServerState.tunnel = ServerState.TUNNEL_UP; - ServerState.tunnelError = null; - } else if (msg.contains("port already used") || msg.contains("port not allowed") || msg.contains("port unavailable")) { - ServerState.tunnel = ServerState.TUNNEL_FAILED; - ServerState.tunnelError = "frp: 外网端口不可用(被占用或服务器不允许)· remote port unavailable (in use or not allowed by the server)"; - } else if (msg.contains("token") || msg.contains("authorization failed")) { - ServerState.tunnel = ServerState.TUNNEL_FAILED; - ServerState.tunnelError = "frp: 令牌不对 · wrong token"; - } else if (msg.contains("login to the server failed") || msg.contains("connect to server error")) { - ServerState.tunnel = ServerState.TUNNEL_CONNECTING; - ServerState.tunnelError = "frp: 连不上服务器,正在重试 · can't reach the server, retrying"; - } + static String onLine(String raw) { + Matcher m = ET_LINE.matcher(raw); + if (!m.matches()) return null; + String level = m.group(1); + String msg = m.group(2); + if (msg.contains("new peer added") || msg.contains("new peer connection added")) { + ServerState.tunnel = ServerState.TUNNEL_UP; + ServerState.tunnelError = null; } if (msg.length() > 180) msg = msg.substring(0, 180) + "…"; String time = new SimpleDateFormat("HH:mm:ss", Locale.ROOT).format(new Date()); - return time + " [" + kind + "] " + level + " " + msg; + return time + " [easytier] " + level + " " + msg; } } diff --git a/android/app/src/main/java/io/github/freecode100year/vivant/VivantService.java b/android/app/src/main/java/io/github/freecode100year/vivant/VivantService.java index 148c609..45a9f11 100644 --- a/android/app/src/main/java/io/github/freecode100year/vivant/VivantService.java +++ b/android/app/src/main/java/io/github/freecode100year/vivant/VivantService.java @@ -29,7 +29,7 @@ /** * Foreground service that runs the VIVANT server (libvivant.so) and, if - * internet access is on, a tunnel (libeasytier.so or libfrpc.so) as child + * internet access is on, a tunnel (libeasytier.so) as child * processes; keeps CPU and Wi-Fi awake, restarts them when they exit, and * polls /healthz for the online count. */ @@ -144,13 +144,12 @@ private void launchTunnel() { if (Prefs.TUNNEL_OFF.equals(tunnelKind)) ServerState.tunnel = ServerState.TUNNEL_OFF; return; } - final String kind = tunnelKind; ServerState.tunnel = ServerState.TUNNEL_CONNECTING; try { - tunnel.start(Tunnels.command(this, kind), tunnelDir(), env(), new Child.Listener() { + tunnel.start(Tunnels.command(this), tunnelDir(), env(), new Child.Listener() { @Override public void onLine(String line) { - String shown = Tunnels.onLine(kind, line); + String shown = Tunnels.onLine(line); if (shown != null) ServerState.log(shown); if (ServerState.tunnel == ServerState.TUNNEL_UP) tunnelFails = 0; } diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index 8fd3490..0384005 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -132,7 +132,7 @@ android:textIsSelectable="true" /> - + - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 52f8177..c0a52c3 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -19,7 +19,6 @@ 同一 Wi-Fi 或连着本机热点的人,输入下面任意一行(iPhone 用 Termius 等 SSH App)· Same Wi-Fi or this phone\'s hotspot — type any line (iPhone: an SSH app like Termius): 外网的人:先装 EasyTier(easytier.cn),再依次运行下面两行 · From anywhere: install EasyTier (easytier.cn), then run these two lines: (用 EasyTier App 的话:加入同名网络后 ssh -p %1$d guest@%2$s)· (EasyTier app: join the network, then ssh -p %1$d guest@%2$s) - 外网的人,输入 · From anywhere, type: 复制 · Copy 已复制 · Copied 没有网络连接 · No network @@ -29,17 +28,10 @@ 外网访问 · Internet access 关闭(只限局域网 / 热点)· Off (LAN / hotspot only) EasyTier(不用服务器)· EasyTier (no server needed) - FRP(用自己的服务器)· FRP (your own server) 免费、点对点打洞,连不通时走公共中继。对方也要装 EasyTier。把网络名和密码发给想进来的人。· Free P2P with a public relay as fallback. Visitors install EasyTier too — send them the name and password. 网络名 · Network name 网络密码 · Network password 换一组 · New pair - 需要一台装了 frps 的公网服务器;对方什么都不用装,直接 ssh。· Needs a public server running frps; visitors install nothing. - 服务器地址 · Server address - frp.example.com - 服务器端口 · Server port (bindPort) - 令牌(没有就留空)· Token (empty if none) - 外网端口 · Public port ▸ 高级设置 · Advanced ▾ 高级设置 · Advanced diff --git a/android/build-tunnels.sh b/android/build-tunnels.sh index 5c65f68..26757b0 100755 --- a/android/build-tunnels.sh +++ b/android/build-tunnels.sh @@ -1,19 +1,18 @@ #!/usr/bin/env bash -# Builds the bundled tunnel tools for Android from their official sources: +# Builds the bundled tunnel tool for Android from its official source: # easytier-core (EasyTier, Rust) -> app/src/main/jniLibs//libeasytier.so -# frpc (frp, Go + cgo) -> app/src/main/jniLibs//libfrpc.so (no admin web UI) -# Like libvivant.so these are plain executables, named .so so Android -# extracts them where an app may run them. +# Like libvivant.so it is a plain executable, named .so so Android +# extracts it where an app may run it. # -# 从官方源码为安卓编译 EasyTier 和 frpc。 +# 从官方源码为安卓编译 EasyTier。 # -# ANDROID_NDK_HOME=… android/build-tunnels.sh easytier|frpc [abi ...] -# Needs: git, cargo + rustup (EasyTier pins its own toolchain), protoc, Go. +# ANDROID_NDK_HOME=… android/build-tunnels.sh easytier [abi ...] +# Needs: git, cargo + rustup (EasyTier pins its own toolchain), protoc. set -euo pipefail here=$(cd "$(dirname "$0")" && pwd) # shellcheck source=/dev/null . "$here/tunnels.env" -tool=${1:?usage: build-tunnels.sh easytier|frpc [abi ...]} +tool=${1:?usage: build-tunnels.sh easytier [abi ...]} shift NDK=${ANDROID_NDK_HOME:-${ANDROID_NDK_LATEST_HOME:-${ANDROID_NDK_ROOT:-}}} [ -d "$NDK" ] || { echo "set ANDROID_NDK_HOME" >&2; exit 1; } @@ -33,9 +32,9 @@ fetch() { # repo tag dir for abi in "${abis[@]}"; do case $abi in - arm64-v8a) triple=aarch64-linux-android clang=aarch64-linux-android$API-clang goarch=arm64 goarm= ;; - armeabi-v7a) triple=armv7-linux-androideabi clang=armv7a-linux-androideabi$API-clang goarch=arm goarm=7 ;; - x86_64) triple=x86_64-linux-android clang=x86_64-linux-android$API-clang goarch=amd64 goarm= ;; + arm64-v8a) triple=aarch64-linux-android clang=aarch64-linux-android$API-clang ;; + armeabi-v7a) triple=armv7-linux-androideabi clang=armv7a-linux-androideabi$API-clang ;; + x86_64) triple=x86_64-linux-android clang=x86_64-linux-android$API-clang ;; *) echo "unknown abi $abi" >&2; exit 1 ;; esac out=$here/app/src/main/jniLibs/$abi @@ -57,14 +56,6 @@ for abi in "${abis[@]}"; do cp "$work/easytier/target/$triple/release/easytier-core" "$out/libeasytier.so" "$TC/llvm-strip" "$out/libeasytier.so" ;; - frpc) - fetch fatedier/frp "$FRP_VERSION" frp - ( - cd "$work/frp" - CGO_ENABLED=1 GOOS=android GOARCH=$goarch GOARM=$goarm CC=$TC/$clang \ - go build -trimpath -ldflags "-s -w" -tags frpc,noweb -o "$out/libfrpc.so" ./cmd/frpc - ) - ;; *) echo "unknown tool $tool" >&2; exit 1 ;; esac echo "built $tool $abi" diff --git a/android/tunnels.env b/android/tunnels.env index d3f8962..7cd839e 100644 --- a/android/tunnels.env +++ b/android/tunnels.env @@ -1,4 +1,3 @@ -# Versions of the tunnel tools bundled in the APK (built from source by +# Version of the tunnel tool bundled in the APK (built from source by # android/build-tunnels.sh). 打包进 APK 的内网穿透工具版本。 EASYTIER_VERSION=v2.7.0 -FRP_VERSION=v0.72.0 diff --git a/docs/android.en.md b/docs/android.en.md index a53e35e..02076f7 100644 --- a/docs/android.en.md +++ b/docs/android.en.md @@ -33,7 +33,6 @@ Phones usually sit behind carrier NAT, so nobody outside can connect directly. P | --- | --- | --- | | **Off** | — | same Wi-Fi / hotspot only | | **EasyTier** (no server) | nothing; the app makes up a network name and password | they install [EasyTier](https://easytier.cn), run the line the app shows, then `ssh -p 2222 guest@127.0.0.1` | -| **FRP** (your own server) | a VPS with a public IP running frps | nothing to install: `ssh -p PUBLIC_PORT guest@SERVER` | **EasyTier:** free P2P hole punching that copes with China's layered NATs, falling back to a public relay (`tcp://public.easytier.top:11010` by default; use your own node under Advanced). Send the network name and password to whoever should join; **New pair** retires the old ones. Visitors run something like: @@ -44,11 +43,11 @@ ssh -p 2222 guest@127.0.0.1 No admin rights needed. With the EasyTier GUI / mobile app, join the same network and `ssh -p 2224 guest@10.144.144.1` (use the port the app shows). The phone needs no VPN permission and no root. -**FRP:** run frps on a VPS (`bindPort`, optional `auth.token`), then fill in server, port, token and public port in the app. Open the public port in the VPS firewall. The link to frps uses TLS. - **Limits:** everyone coming through a tunnel looks like the phone itself to the server, so the app gives tunnels their own local entrance (`-relay-addr`) where they share the same generous limits as Tor users (300 at once, 120 new connections a minute) instead of one address's small quota. -**Privacy:** SSH is end-to-end encrypted, so relays and frps can't read the chat — but the public EasyTier relay and your frps do see visitors' IP addresses. If that matters, use your own EasyTier node or frps. +**Privacy:** SSH is end-to-end encrypted, so relays can't read the chat — but the public EasyTier relay does see visitors' IP addresses. If that matters, use your own EasyTier node. + +EasyTier is the only internet access option (the FRP option was removed; if you had chosen FRP, internet access is off after the update — choose EasyTier again). ## Networking @@ -72,17 +71,16 @@ Same as on a VPS: no accounts, no IPs / nicknames / messages in the log, chat ke ## Build it yourself -Needs Rust, the Android NDK (r25+), JDK 17, and Gradle 8.14+ or Android Studio; the tunnel tools also need Go, protoc and cmake: +Needs Rust, the Android NDK (r25+), JDK 17, and Gradle 8.14+ or Android Studio; EasyTier also needs protoc and cmake: ```bash rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android export ANDROID_NDK_HOME=/path/to/ndk android/build-native.sh # VIVANT server -> jniLibs/*/libvivant.so android/build-tunnels.sh easytier # EasyTier -> jniLibs/*/libeasytier.so -android/build-tunnels.sh frpc # frpc -> jniLibs/*/libfrpc.so gradle -p android assembleRelease # -> android/app/build/outputs/apk/release/ ``` -EasyTier and frp are built from their official sources at the versions pinned in `android/tunnels.env`, not from someone else's binaries. All three programs are packed as `lib*.so` (really plain executables) so Android extracts them to the app's native library directory — the only place an app may execute programs from; a foreground service runs them as child processes. +EasyTier is built from its official source at the version pinned in `android/tunnels.env`, not from someone else's binaries. Both programs are packed as `lib*.so` (really plain executables) so Android extracts them to the app's native library directory — the only place an app may execute programs from; a foreground service runs them as child processes. **Signing:** without configuration the APK is debug-signed, with a different key on every CI run, so uninstall the old one before updating. For stable signing add the repository secrets `ANDROID_KEYSTORE_BASE64` (base64 of a `.jks`), `ANDROID_KEYSTORE_PASSWORD`, `ANDROID_KEY_ALIAS` (optionally `ANDROID_KEY_PASSWORD`). Locally use `VIVANT_KEYSTORE`, `VIVANT_KEYSTORE_PASSWORD`, `VIVANT_KEY_ALIAS`, `VIVANT_KEY_PASSWORD`. diff --git a/docs/android.md b/docs/android.md index ca0295b..c3931e1 100644 --- a/docs/android.md +++ b/docs/android.md @@ -33,7 +33,6 @@ | --- | --- | --- | | **关闭** | — | 只有同一 Wi-Fi / 热点 | | **EasyTier**(不用服务器) | 什么都不用准备;App 自动生成网络名和密码 | 对方先装 [EasyTier](https://easytier.cn),运行 App 里显示的那行命令,再 `ssh -p 2222 guest@127.0.0.1` | -| **FRP**(自己的服务器) | 一台有公网 IP、运行 frps 的 VPS | 什么都不用装,直接 `ssh -p 外网端口 guest@服务器地址` | **EasyTier:** 免费、点对点打洞,国内复杂 NAT 也能通;打不通时自动走公共中继(默认 `tcp://public.easytier.top:11010`,可在高级设置里换成自己的节点)。把网络名和密码发给想进来的人,「换一组」可以随时作废旧的。对方运行的命令类似: @@ -44,11 +43,11 @@ ssh -p 2222 guest@127.0.0.1 不需要管理员权限。用 EasyTier 图形版 / 手机 App 的话,加入同名网络后直接 `ssh -p 2224 guest@10.144.144.1`(端口以 App 显示为准)。手机这边不建 VPN、不要 root,也不占用系统 VPN 位置。 -**FRP:** 在 VPS 上装好 frps(`bindPort`,可选 `auth.token`),在 App 里填服务器地址、端口、令牌和外网端口。别忘了在 VPS 防火墙里放行外网端口。和 frps 之间默认走 TLS。 - **限额:** 经穿透进来的连接在服务器看来都来自本机,所以 App 让它们走单独的本地入口(`-relay-addr`),共用一组和 Tor 用户相同的宽松限额(同时 300 人、每分钟 120 个新连接),而不是挤在一个地址的小限额里。 -**隐私:** SSH 本身端到端加密,中继和 frps 看不到聊天内容;但 EasyTier 公共中继和你的 frps 能看到访客的 IP。对 IP 敏感的人,请用自己的 EasyTier 节点或 frps。 +**隐私:** SSH 本身端到端加密,中继看不到聊天内容;但 EasyTier 公共中继能看到访客的 IP。对 IP 敏感的人,请用自己的 EasyTier 节点。 + +外网访问只保留 EasyTier(以前的 FRP 选项已移除;之前选了 FRP 的,升级后外网访问为「关闭」,重新选 EasyTier 即可)。 ## 网络说明 @@ -72,17 +71,16 @@ ssh -p 2222 guest@127.0.0.1 ## 自己构建 -需要 Rust、Android NDK(r25+)、JDK 17、Gradle 8.14+ 或 Android Studio;穿透工具另需 Go、protoc、cmake: +需要 Rust、Android NDK(r25+)、JDK 17、Gradle 8.14+ 或 Android Studio;EasyTier 另需 protoc、cmake: ```bash rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android export ANDROID_NDK_HOME=/path/to/ndk android/build-native.sh # VIVANT 服务器 → jniLibs/*/libvivant.so android/build-tunnels.sh easytier # EasyTier → jniLibs/*/libeasytier.so -android/build-tunnels.sh frpc # frpc → jniLibs/*/libfrpc.so gradle -p android assembleRelease # → android/app/build/outputs/apk/release/ ``` -EasyTier 和 frp 都从官方源码按 `android/tunnels.env` 里固定的版本编译,不用别人的预编译文件。三个程序都以 `lib*.so` 的名字打进 APK(其实是普通可执行文件),这样安卓会把它们解压到 App 的原生库目录——App 唯一允许执行程序的地方;前台服务用子进程启动它们。 +EasyTier 从官方源码按 `android/tunnels.env` 里固定的版本编译,不用别人的预编译文件。两个程序都以 `lib*.so` 的名字打进 APK(其实是普通可执行文件),这样安卓会把它们解压到 App 的原生库目录——App 唯一允许执行程序的地方;前台服务用子进程启动它们。 **签名:** 不设置时用调试密钥签名,每次 CI 构建的密钥不同,升级前要先卸载旧版。想要稳定签名,在仓库 Secrets 里添加 `ANDROID_KEYSTORE_BASE64`(`.jks` 文件的 base64)、`ANDROID_KEYSTORE_PASSWORD`、`ANDROID_KEY_ALIAS`(可选 `ANDROID_KEY_PASSWORD`)。本地构建用环境变量 `VIVANT_KEYSTORE`、`VIVANT_KEYSTORE_PASSWORD`、`VIVANT_KEY_ALIAS`、`VIVANT_KEY_PASSWORD`。 From 18f98764299b8c7698c1b13767d975760963d566 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 13 Nov 2026 20:01:16 -0500 Subject: [PATCH 3/3] v0.12.0: new README; Android app uses EasyTier only - README / README.en.md rewritten (browser entrance chat.nya.je, no-kick note) - Android: FRP removed; internet access through EasyTier only. A saved FRP setting falls back to Off on upgrade - server unchanged from v0.11.5 Co-Authored-By: Claude Opus 5.5 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 62d5dbd..ac454c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.5 +0.12.0