From 09dc38240369bced1fdbc4773ba6a73c51f0b036 Mon Sep 17 00:00:00 2001 From: Freecode100Year Date: Fri, 13 Nov 2026 04:46:40 -0500 Subject: [PATCH] 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`。