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`。