├── .config └── check-method ├── .github ├── banner.png ├── workflows │ ├── switch-version-check-method.yml │ ├── check-version.yml │ └── build-tailscale.yml ├── README.md └── README_en.md ├── package └── tailscale │ ├── test.sh │ ├── files │ ├── tailscale.conf │ └── tailscale.init │ ├── README.md │ └── Makefile ├── NOTICE ├── LICENSE ├── LICENSE.tailscale ├── install_en.sh └── install.sh /.config/check-method: -------------------------------------------------------------------------------- 1 | tag 2 | -------------------------------------------------------------------------------- /.github/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuNanOvO/openwrt-tailscale/HEAD/.github/banner.png -------------------------------------------------------------------------------- /package/tailscale/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$1" in 4 | tailscale) 5 | tailscale version | grep "$2" 6 | ;; 7 | tailscaled) 8 | tailscaled -version | grep "$2" 9 | ;; 10 | esac 11 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | NOTICE 2 | 3 | This project includes components derived from the Tailscale project: 4 | https://github.com/tailscale/tailscale 5 | 6 | The Tailscale project is licensed under the BSD 3-Clause License. 7 | 8 | See LICENSE.tailscale for the full license text. 9 | -------------------------------------------------------------------------------- /package/tailscale/files/tailscale.conf: -------------------------------------------------------------------------------- 1 | config settings 'settings' 2 | option log_stderr '1' 3 | option log_stdout '1' 4 | option port '41641' 5 | option state_file '/etc/tailscale/tailscaled.state' 6 | # default to using nftables - change below to 'iptables' if still using iptables 7 | option fw_mode 'nftables' -------------------------------------------------------------------------------- /package/tailscale/README.md: -------------------------------------------------------------------------------- 1 | # Tailscale 2 | This readme should help you with tailscale client setup. 3 | 4 | > [!NOTE] 5 | > By default this package will use nftables. If you wish to use iptables, the config file `/etc/config/tailscale` can be modfied, changing the line `fw_mode 'nftables'` to `fw_mode 'iptables'`. You can then run `/etc/init.d/tailscale restart` to restart tailscale using your chosen method 6 | 7 | ## First setup 8 | 9 | First, enable and run daemon 10 | 11 | ``` 12 | /etc/init.d/tailscale enable 13 | /etc/init.d/tailscale start 14 | ``` 15 | 16 | Then you should use tailscale utility to get a login link for your device. 17 | 18 | Run command and finish device registration with the given URL. 19 | ``` 20 | tailscale up 21 | ``` 22 | 23 | See the [OpenWrt wiki](https://openwrt.org/docs/guide-user/services/vpn/tailscale/start) for more detailed setup instructions 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 GuNanOvO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package/tailscale/files/tailscale.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | # Copyright 2020 Google LLC. 4 | # Copyright (C) 2021 CZ.NIC z.s.p.o. (https://www.nic.cz/) 5 | # SPDX-License-Identifier: Apache-2.0 6 | 7 | USE_PROCD=1 8 | START=80 9 | 10 | start_service() { 11 | local state_file 12 | local port 13 | local std_err std_out 14 | 15 | config_load tailscale 16 | config_get_bool std_out "settings" log_stdout 1 17 | config_get_bool std_err "settings" log_stderr 1 18 | config_get port "settings" port 41641 19 | config_get state_file "settings" state_file /etc/tailscale/tailscaled.state 20 | config_get fw_mode "settings" fw_mode nftables 21 | 22 | /usr/sbin/tailscaled --cleanup 23 | 24 | procd_open_instance 25 | procd_set_param command /usr/sbin/tailscaled 26 | 27 | # Starting with v1.48.1 ENV variable is required to enable use of iptables / nftables. 28 | # Use nftables by default - can be changed to 'iptables' in tailscale config 29 | procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" 30 | 31 | # Set the port to listen on for incoming VPN packets. 32 | # Remote nodes will automatically be informed about the new port number, 33 | # but you might want to configure this in order to set external firewall 34 | # settings. 35 | procd_append_param command --port "$port" 36 | procd_append_param command --state "$state_file" 37 | 38 | procd_set_param respawn 39 | procd_set_param stdout "$std_out" 40 | procd_set_param stderr "$std_err" 41 | 42 | procd_close_instance 43 | } 44 | 45 | stop_service() { 46 | /usr/sbin/tailscaled --cleanup 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.tailscale: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020 Tailscale Inc & AUTHORS. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.github/workflows/switch-version-check-method.yml: -------------------------------------------------------------------------------- 1 | name: Change Default Version Check Method 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version_check_method: 7 | description: 'Set the default version check method' 8 | type: choice 9 | options: 10 | - ghcr 11 | - tag 12 | default: 'ghcr' 13 | required: true 14 | 15 | jobs: 16 | update-config: 17 | name: Update Default Check Method 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Update .config/check-method file 27 | run: | 28 | mkdir -p .config 29 | echo "${{ github.event.inputs.version_check_method }}" > .config/check-method 30 | echo "Updated .config/check-method to:" 31 | cat .config/check-method 32 | 33 | - name: Check for changes 34 | id: check-changes 35 | run: | 36 | if git diff --quiet .config/check-method; then 37 | echo "No changes were necessary" 38 | echo "changed=false" >> $GITHUB_OUTPUT 39 | else 40 | echo "Changes detected" 41 | echo "changed=true" >> $GITHUB_OUTPUT 42 | fi 43 | 44 | - name: Commit and push changes 45 | if: steps.check-changes.outputs.changed == 'true' 46 | run: | 47 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 48 | git config --local user.name "github-actions[bot]" 49 | git add .config/check-method 50 | git commit -m "chore: update default version check method to ${{ github.event.inputs.version_check_method }}" 51 | git push 52 | -------------------------------------------------------------------------------- /package/tailscale/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2021 CZ.NIC, z. s. p. o. (https://www.nic.cz/) 3 | # Copyright (C) 2025 GuNan 4 | # 5 | # This is free software, licensed under the BSD 3-Clause License. 6 | # See /LICENSE for more information. 7 | # 8 | 9 | include $(TOPDIR)/rules.mk 10 | 11 | PKG_NAME:=tailscale 12 | PKG_VERSION:=1.92.4 13 | PKG_RELEASE:=1 14 | PKG_COMMIT:=64bf33b86bd9bfe332070ab7ded7a720af044358 15 | 16 | PKG_STRIP:=0 17 | 18 | PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz 19 | PKG_SOURCE_URL:=https://codeload.github.com/tailscale/tailscale/tar.gz/v$(PKG_VERSION)? 20 | PKG_HASH:=de1fd8aa157bda964b7f96a63a8b8e768b3565a26630c87089f44103c25e9bd1 21 | 22 | PKG_MAINTAINER:=GuNan 23 | PKG_LICENSE:=BSD-3-Clause 24 | PKG_LICENSE_FILES:=LICENSE 25 | PKG_CPE_ID:=cpe:/a:tailscale:tailscale 26 | 27 | PKG_BUILD_DIR:=$(BUILD_DIR)/tailscale-$(PKG_VERSION) 28 | PKG_BUILD_PARALLEL:=1 29 | PKG_BUILD_FLAGS:=no-mips16 30 | 31 | GO_PKG:=tailscale.com/cmd/tailscaled 32 | GO_PKG_LDFLAGS:=-s -w -buildid= -X 'tailscale.com/version.longStamp=$(PKG_VERSION) (OpenWrt-UPX)' 33 | GO_PKG_LDFLAGS_X:=tailscale.com/version.shortStamp=$(PKG_VERSION) 34 | GO_PKG_TAGS:=ts_include_cli,ts_omit_aws,ts_omit_bird,ts_omit_completion,ts_omit_kube,ts_omit_systray,ts_omit_taildrop,ts_omit_tap,ts_omit_tpm,ts_omit_relayserver,ts_omit_capture,ts_omit_syspolicy,ts_omit_debugeventbus,ts_omit_webclient 35 | GO_PKG_GCFLAGS:=-trimpath 36 | 37 | ifneq ($(filter mips64% riscv64% loongarch64%,$(ARCH)),) 38 | DISABLE_UPX:=1 39 | endif 40 | 41 | include $(INCLUDE_DIR)/package.mk 42 | include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk 43 | 44 | define Package/tailscale 45 | SECTION:=net 46 | CATEGORY:=Network 47 | SUBMENU:=VPN 48 | TITLE:=Zero config VPN (UPX Compressed) 49 | URL:=Original(https://tailscale.com);Smaller(https://github.com/GuNanOvO/openwrt-tailscale) 50 | DEPENDS:=$(GO_ARCH_DEPENDS) ca-bundle kmod-tun 51 | PROVIDES:=tailscale tailscaled 52 | endef 53 | 54 | define Package/tailscale/description 55 | A smaller version of Tailscale. Built for OpenWrt. 56 | It creates a secure network between your servers, computers, 57 | and cloud instances. Even when separated by firewalls or subnets. 58 | endef 59 | 60 | define Package/tailscale/conffiles 61 | /etc/config/tailscale 62 | /etc/tailscale/ 63 | endef 64 | 65 | define Package/tailscale/install 66 | $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config 67 | $(INSTALL_BIN) $(GO_PKG_BUILD_BIN_DIR)/tailscaled $(1)/usr/sbin 68 | 69 | ifneq ($(DISABLE_UPX),1) 70 | echo "==> UPX enabled on ARCH $(ARCH)" 71 | $(TOPDIR)/upx/upx --best --lzma $(1)/usr/sbin/tailscaled 72 | else 73 | echo "==> UPX disabled on ARCH $(ARCH)" 74 | endif 75 | 76 | $(LN) tailscaled $(1)/usr/sbin/tailscale 77 | $(INSTALL_BIN) ./files/tailscale.init $(1)/etc/init.d/tailscale 78 | $(INSTALL_DATA) ./files/tailscale.conf $(1)/etc/config/tailscale 79 | endef 80 | 81 | $(eval $(call BuildPackage,tailscale)) -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | **简体中文文档** | [English Docs](README_en.md) 2 | 3 | ![Tailscale & OpenWrt](./banner.png) 4 | 5 | # 适用于 OpenWrt 设备的 最新的、更小的 Tailscale 6 | 7 | ![GitHub release](https://img.shields.io/github/v/release/GuNanOvO/openwrt-tailscale?style=flat) 8 | ![Views](https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fgithub.com%2FGuNanOvO%2Fopenwrt-tailscale&label=Views&countColor=%23b7d079&style=flat) 9 | ![Downloads](https://img.shields.io/github/downloads/GuNanOvO/openwrt-tailscale/total?style=flat) 10 | ![GitHub Stars](https://img.shields.io/github/stars/GuNanOvO/openwrt-tailscale?label=Stars&color=yellow) 11 | 12 | ### 本仓库提供以下内容: 13 | 14 | * 适用于多种架构的、最新的、更小的 **Tailscale.ipk** 软件包 15 | * 一键安装脚本,支持 **持久化安装**、**临时安装** Tailscale 16 | * **OPKG 软件源**,更简单、更加方便持续更新 ➡️ [ [Smaller Tailscale Repo](https://gunanovo.github.io/openwrt-tailscale/) ] 17 | 18 | --- 19 | 20 |
21 |

支持架构列表:

22 | 23 | 以下目标架构平台受支持,由于架构较多,测试仍未完善,希望您能测试使用并反馈♥️ 24 | 25 | * `aarch64_cortex-a53` 26 | * `aarch64_cortex-a72` 27 | * `aarch64_cortex-a76` 28 | * `aarch64_generic` 29 | * `arm_arm1176jzf-s_vfp` 30 | * `arm_arm926ej-s` 31 | * `arm_cortex-a15_neon-vfpv4` 32 | * `arm_cortex-a5_vfpv4` 33 | * `arm_cortex-a7` 34 | * `arm_cortex-a7_neon-vfpv4` 35 | * `arm_cortex-a7_vfpv4` 36 | * `arm_cortex-a8_vfpv3` 37 | * `arm_cortex-a9` 38 | * `arm_cortex-a9_neon` 39 | * `arm_cortex-a9_vfpv3-d16` 40 | * `arm_fa526` 41 | * `arm_xscale` 42 | * `i386_pentium-mmx` 43 | * `i386_pentium4` 44 | * `loongarch64_generic` 45 | * `mips64_mips64r2` 46 | * `mips64_octeonplus` 47 | * `mips64el_mips64r2` 48 | * `mips_24kc` 49 | * `mips_4kec` 50 | * `mips_mips32` 51 | * `mipsel_24kc` 52 | * `mipsel_24kc_24kf` 53 | * `mipsel_74kc` 54 | * `mipsel_mips32` 55 | * `riscv64_riscv64` 56 | * `x86_64`✅ 57 | 58 | 以下架构不受支持: 59 | * `armeb_xscale` 60 | * `powerpc64_e5500` 61 | * `powerpc_464fp` 62 | * `powerpc_8548` 63 | 64 |
65 | 66 | --- 67 | 68 | ### 使用方式: 69 | 70 | > [!WARNING] 71 | > 请在使用前阅读以下内容 72 | > **需求说明:** 73 | > 74 | > * **存储空间**:小于 8MB (除`mips64` `riscv64` `loongarch64`); 75 | > * **运行内存**:大约 60MB (运行时); 76 | > * **网络环境**:能够访问 GitHub 或代理镜像站; 77 | > 78 | > **注意事项:** 79 | > 80 | > * 运行内存小于 256MB 的设备可能无法运行; 81 | > * 临时安装高度依赖于网络环境,可靠性较低!建议仅用于无法持久安装的设备; 82 | > * 多数设备或架构未经过测试,如果您测试不可用,烦请提出issues,我会尽快与您沟通进行修复; 83 | 84 | #### **一键式命令行脚本:** 85 | 86 | SSH链接至OpenWrt设备执行: 87 | 88 | ```bash 89 | wget -O /usr/sbin/install.sh https://ghfast.top/https://raw.githubusercontent.com/GuNanOvO/openwrt-tailscale/main/install.sh && chmod +x /usr/sbin/install.sh && /usr/sbin/install.sh 90 | ``` 91 | 92 | For Mainland China users only. 93 | For other regions, please refer to [English README](README_en.md) 94 | 95 | #### **一键式命令行脚本使用自定义代理:** 96 | 97 | 使用参数`--custom-proxy`: 98 | 99 | ```bash 100 | wget -O /usr/bin/install.sh https://ghfast.top/https://raw.githubusercontent.com/GuNanOvO/openwrt-tailscale/main/install.sh && chmod +x /usr/bin/install.sh && /usr/bin/install.sh --custom-proxy 101 | ``` 102 | 103 | #### **添加opkg软件源:** 104 | 105 | 详见本项目分支 [软件源分支](../feed/README.md) 或本项目opkg软件源页面 [Smaller Tailscale Repository For OpenWrt](https://gunanovo.github.io/openwrt-tailscale/) 106 | 107 | 仅包含受支持的架构的ipk包 108 | 109 | #### **自行安装ipk软件包:** 110 | 1. 于本仓库[Releases](https://github.com/GuNanOvO/openwrt-tailscale/releases)下载与您设备对应架构的ipk软件包; 111 | 2. 可以于OpenWrt设备后台网页界面 -> 系统 -> 软件包 112 | -> 上传软件包,选择您下载的软件包进行上传并安装; 113 | 114 | 注意: 115 | 显示安装错误,则先测试 `tailscale up` ,如若正常,则安装成功。 116 | 117 | #### **Luci 图形化界面推荐:** 118 | 119 | 为方便使用,免除大部分命令行操作,可自行选择使用: 120 | 来自于@Tokisaki-Galaxy开源项目:[luci-app-tailscale-community](https://github.com/Tokisaki-Galaxy/luci-app-tailscale-community)。 121 | 122 | 123 | > [!NOTE] 124 | > 如果你有如下情况出现: 125 | > 126 | > 1. 设备运行内存有限,在使用过程中出现tailscale占用极高运行内存; 127 | > 2. 或直接致使tailscale被OOM KILLER杀死并重启; 128 | > 3. 或你不清楚什么原因导致tailscale异常重启; 129 | > 130 | > 则,你可以尝试以更高的CPU占用换取较低的内存占用,操作如下: 131 | > 132 | > 1. 修改`/etc/init.d/tailscale`文件 133 | > 134 | > ```bash 135 | > vi /etc/init.d/tailscale 136 | > ``` 137 | > 2. 找到 `procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode"` 一行 138 | > 139 | > ```bash 140 | > procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" 141 | > ``` 142 | > 3. 在该行后方加上参数 `GOGC=10` 143 | > 144 | > ```bash 145 | > procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" GOGC=10 146 | > ``` 147 | > 148 | > 该参数将使tailscale更积极地回收内存 149 | > 更多信息,可查看issues:[关于内存占用](https://github.com/GuNanOvO/openwrt-tailscale/issues/17) 150 | 151 | 152 | --- 153 | 154 | ### 编译优化: 155 | 156 | 使用了下列编译参数,精简了tailscale,详见[Makefile](../package/tailscale/Makefile): 157 | 158 | * **TAGS**: 159 | 160 | ``` 161 | ts_include_cli,ts_omit_aws,ts_omit_bird,ts_omit_completion,ts_omit_kube,ts_omit_systray,ts_omit_taildrop,ts_omit_tap,ts_omit_tpm,ts_omit_relayserver,ts_omit_capture,ts_omit_syspolicy,ts_omit_debugeventbus,ts_omit_webclient 162 | ``` 163 | 164 | * **LDFLAGS**: 165 | 166 | ``` 167 | -s -w -buildid= 168 | ``` 169 | 170 | 使用了[UPX](https://upx.github.io/)二进制文件压缩技术,并使用了以下参数,详见[Makefile](../package/tailscale/Makefile): 171 | 172 | ``` 173 | --best --lzma 174 | ``` 175 | 176 | --- 177 | 178 | ### 脚本逻辑: 179 | 180 | * **持久安装**:代替手动下载ipk包,自动将ipk包下载至设备,使用`opkg install`进行安装; 181 | * **临时安装**:下载ipk包至设备,解包ipk,提取二进制文件,放置于`/tmp`目录下,并在`/usr/sbin`目录下创建连接; 182 | 183 | 以上两点,可详查于[install.sh](../install.sh) 184 | 185 | --- 186 | 187 | ### 特别致谢 🙏 188 | 189 | **[[UPX](https://upx.github.io/)]**:UPX技术,为本仓库编译如此小巧的tailscale包创造了可能; 190 | 191 | **[[Github Actions](https://github.com/features/actions)]**:用于自动化构建与发布; 192 | 193 | **[[glinet-tailscale-updater](https://github.com/Admonstrator/glinet-tailscale-updater)]**: 本仓库最初技术参考之一,如果你的glinet设备需要使用tailscale,这是你的不二之选; 194 | 195 | **[[tailscale-openwrt](https://github.com/CH3NGYZ/tailscale-openwrt)]**: 本仓库最初技术参考之一,同样提供tailscale在openwrt上的安装脚本,您可自行选用; 196 | 197 | **[[openwrt-tailscale-repo](https://github.com/lanrat/openwrt-tailscale-repo)]**: 本仓库feed源技术参考; 198 | 199 | **[[Github加速代理](../install.sh)]**: 本仓库安装脚本中使用的加速代理服务,详查于[install.sh](../install.sh); 200 | 201 | --- 202 | 203 | ### 问题反馈 204 | 205 | 遇到问题请至 [Issues](https://github.com/GuNanOvO/openwrt-tailscale/issues) 提交,请附上: 206 | 207 | 1. 设备架构信息(`uname -m`) 208 | 2. 目标平台架构信息(`opkg print-architecture`) 209 | 3. 安装模式(持久/临时/opkg安装) 210 | 4. 相关日志片段 211 | 212 | --- 213 | 214 | ### 自行复刻 215 | 216 | 如果你需要对本项目进行fork复刻,你需要注意以下几点: 217 | 218 | **修改install脚本**: 219 | 220 | * 修改脚本顶部变量区域的:`REPO_URL` & `REPO` 对应到你的fork仓库。 221 | 222 | **修改github actions 工作流文件**: 223 | 224 | * 修改`.github/workflows/build-tailscale.yml`与`.github/workflows/check-version.yml`当中的所有`GuNanOvO/openwrt-tailscale`为你fork项目,通常只需要修改env部分 225 | 226 | **工作流文件当中使用的SECRETS**: 227 | 228 | * `secrets.USIGN_SECRET_KEY_B64`: 229 | 230 | * 使用usign生成的私钥,用于签名ipk包,使用base64对私钥进行编码后,设置于仓库的setting > security > secrets and variables > actions > Repository secrets 231 | * `secrets.PAT_TOKEN`: 232 | 233 | * github账户`repo`权限token,用于供`.github/workflows/check-version.yml`触发 234 | * `.github/workflows/build-tailscale.yml`进行构建工作 235 | * `secrets.GHCR_READ_TOKEN`: 236 | 237 | * github账户`read:packages`权限token 238 | * 用于供action检测上游ghcr发布版本,默认不使用ghcr版本,可去除 239 | 240 | --- 241 | 242 | ### 安全声明 243 | 本项目是对 **Tailscale** 官方开源软件的再分发,主要目的是为 **OpenWrt** 用户提供及时**更新的**、且更适用于**小存储容量**的OpenWrt设备的软件包,以替换官方源中已过时的版本。 244 | 过时的 Tailscale 版本可能存在已知安全漏洞,及时更新对于保障网络安全至关重要。 245 | 246 | **透明与可验证**: 247 | * **源代码公开**:所有打包、构建与安装脚本完全开源,任何人均可审查、复现整个构建、安装流程。 248 | * **自动化构建**:构建与打包过程完全由 GitHub Actions 自动执行,构建日志和产物对外公开,确保无人工干预。 249 | * **官方源码构建**:所有二进制文件均直接从 [**Tailscale**](https://github.com/tailscale/tailscale) 官方项目 的发布版本源码编译,无任何功能性修改或隐藏代码。 250 | * **可重复构建**:任何人可使用本项目的脚本在自己的 GitHub 或本地环境中重现构建结果,以验证一致性。 251 | **安全承诺**: 252 | * 本项目 **不植入任何恶意代码**,不收集、不上传用户的任何数据。 253 | * 仅对构建过程进行优化(如体积精简),不改动 Tailscale 的核心功能与安全机制。 254 | * 所有发布的软件包均提供可公开验证的构建记录与校验信息(SHA256 校验和 / usign 签名)。 255 | 256 | 通过以上措施,本项目旨在为 OpenWrt 用户提供 **安全、透明、可审计** 的 Tailscale 安装与更新途径,降低使用过时版本带来的安全风险。 257 | 258 | --- 259 | 260 | ### License 261 | 262 | 本项目使用 **MIT协议**,并包含来自 [**Tailscale**](https://github.com/tailscale/tailscale) 项目的代码,该部分遵循 **BSD 3-Clause 协议**。 263 | 264 | --- 265 | 266 | > 💖 如果本项目对您有帮助,欢迎点亮小星星⭐! 267 | -------------------------------------------------------------------------------- /.github/README_en.md: -------------------------------------------------------------------------------- 1 | **简体中文文档** | [English Docs](README_en.md) 2 | 3 | ![Tailscale & OpenWrt](./banner.png) 4 | 5 | # The Latest, Smaller Tailscale for OpenWrt Devices 6 | 7 | ![GitHub release](https://img.shields.io/github/v/release/GuNanOvO/openwrt-tailscale?style=flat) 8 | ![Views](https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fgithub.com%2FGuNanOvO%2Fopenwrt-tailscale\&label=Views\&countColor=%23b7d079\&style=flat) 9 | ![Downloads](https://img.shields.io/github/downloads/GuNanOvO/openwrt-tailscale/total?style=flat) 10 | ![GitHub Stars](https://img.shields.io/github/stars/GuNanOvO/openwrt-tailscale?label=Stars\&color=yellow) 11 | 12 | ### This repository provides: 13 | 14 | * The latest and smaller **Tailscale ipk packages** for multiple architectures 15 | * One-click installation scripts supporting **persistent installation** and **temporary installation** 16 | * An **OPKG feed** for easier and continuous updates 17 | ➡️ [ [Smaller Tailscale Repo](https://gunanovo.github.io/openwrt-tailscale/) ] 18 | 19 | --- 20 | 21 |
22 |

Supported Architectures:

23 | 24 | The following target architectures are supported. 25 | Due to the large number of architectures, testing is not yet complete. 26 | Your testing and feedback are greatly appreciated ♥️ 27 | 28 | * `aarch64_cortex-a53` 29 | * `aarch64_cortex-a72` 30 | * `aarch64_cortex-a76` 31 | * `aarch64_generic` 32 | * `arm_arm1176jzf-s_vfp` 33 | * `arm_arm926ej-s` 34 | * `arm_cortex-a15_neon-vfpv4` 35 | * `arm_cortex-a5_vfpv4` 36 | * `arm_cortex-a7` 37 | * `arm_cortex-a7_neon-vfpv4` 38 | * `arm_cortex-a7_vfpv4` 39 | * `arm_cortex-a8_vfpv3` 40 | * `arm_cortex-a9` 41 | * `arm_cortex-a9_neon` 42 | * `arm_cortex-a9_vfpv3-d16` 43 | * `arm_fa526` 44 | * `arm_xscale` 45 | * `i386_pentium-mmx` 46 | * `i386_pentium4` 47 | * `loongarch64_generic` 48 | * `mips64_mips64r2` 49 | * `mips64_octeonplus` 50 | * `mips64el_mips64r2` 51 | * `mips_24kc` 52 | * `mips_4kec` 53 | * `mips_mips32` 54 | * `mipsel_24kc` 55 | * `mipsel_24kc_24kf` 56 | * `mipsel_74kc` 57 | * `mipsel_mips32` 58 | * `riscv64_riscv64` 59 | * `x86_64` ✅ 60 | 61 | The following architectures are **not supported**: 62 | 63 | * `armeb_xscale` 64 | * `powerpc64_e5500` 65 | * `powerpc_464fp` 66 | * `powerpc_8548` 67 | 68 |
69 | 70 | --- 71 | 72 | ### Usage: 73 | 74 | > [!WARNING] 75 | > Please read the following before use 76 | > **Requirements:** 77 | > 78 | > * **Storage**: Less than 8MB (except `mips64`, `riscv64`, `loongarch64`) 79 | > * **RAM**: About 60MB (runtime) 80 | > * **Network**: Ability to access GitHub or mirror/proxy services 81 | > 82 | > **Notes:** 83 | > 84 | > * Devices with less than 256MB RAM may fail to run Tailscale 85 | > * Temporary installation heavily depends on network stability and is less reliable 86 | > — recommended only for devices where persistent installation is impossible 87 | > * Most devices/architectures are untested; 88 | > if you encounter issues, please open an issue and I will respond as soon as possible 89 | 90 | #### **One-click CLI Installation Script** 91 | 92 | SSH into your OpenWrt device and run: 93 | 94 | ```bash 95 | wget -O /usr/sbin/install.sh https://raw.githubusercontent.com/GuNanOvO/openwrt-tailscale/main/install_en.sh && chmod +x /usr/sbin/install.sh && /usr/sbin/install.sh 96 | ``` 97 | 98 | #### **Add OPKG Feed** 99 | 100 | See the [feed branch README](../feed/README.md) 101 | or the repository page: 102 | [Smaller Tailscale Repository For OpenWrt](https://gunanovo.github.io/openwrt-tailscale/) 103 | 104 | Only ipk packages for supported architectures are included. 105 | 106 | #### **Manual ipk Installation** 107 | 108 | 1. Download the ipk package matching your device architecture from 109 | [Releases](https://github.com/GuNanOvO/openwrt-tailscale/releases) 110 | 2. Open OpenWrt Web UI → System → Software → Upload Package 111 | Upload and install the downloaded ipk 112 | 113 | Note: 114 | If the UI shows an installation error, try running `tailscale up`. 115 | If it works normally, the installation was successful. 116 | 117 | #### **Recommended LuCI GUI** 118 | 119 | For easier usage with minimal CLI interaction: 120 | From @Tokisaki-Galaxy’s open-source project: 121 | [luci-app-tailscale-community](https://github.com/Tokisaki-Galaxy/luci-app-tailscale-community) 122 | 123 | --- 124 | 125 | > [!NOTE] 126 | > If you experience any of the following: 127 | > 128 | > 1. Very high memory usage by Tailscale 129 | > 2. Tailscale being killed and restarted by OOM Killer 130 | > 3. Unexpected Tailscale restarts with unknown cause 131 | > 132 | > You can trade higher CPU usage for lower memory usage as follows: 133 | > 134 | > 1. Edit `/etc/init.d/tailscale` 135 | > 136 | > ```bash 137 | > vi /etc/init.d/tailscale 138 | > ``` 139 | > 2. Locate the line: 140 | > 141 | > ```bash 142 | > procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" 143 | > ``` 144 | > 3. Append `GOGC=10` to the line: 145 | > 146 | > ```bash 147 | > procd_set_param env TS_DEBUG_FIREWALL_MODE="$fw_mode" GOGC=10 148 | > ``` 149 | > 150 | > This makes Tailscale reclaim memory more aggressively. 151 | > For more details, see issue: 152 | > [Memory usage discussion](https://github.com/GuNanOvO/openwrt-tailscale/issues/17) 153 | 154 | --- 155 | 156 | ### Build Optimizations 157 | 158 | The following build options are used to minimize Tailscale. 159 | See [Makefile](../package/tailscale/Makefile) for details: 160 | 161 | * **TAGS** 162 | 163 | ``` 164 | ts_include_cli,ts_omit_aws,ts_omit_bird,ts_omit_completion,ts_omit_kube,ts_omit_systray,ts_omit_taildrop,ts_omit_tap,ts_omit_tpm,ts_omit_relayserver,ts_omit_capture,ts_omit_syspolicy,ts_omit_debugeventbus,ts_omit_webclient 165 | ``` 166 | 167 | * **LDFLAGS** 168 | 169 | ``` 170 | -s -w -buildid= 171 | ``` 172 | 173 | Binary compression is performed using [UPX](https://upx.github.io/) with: 174 | 175 | ``` 176 | --best --lzma 177 | ``` 178 | 179 | --- 180 | 181 | ### Script Logic 182 | 183 | * **Persistent installation**: 184 | Automatically downloads ipk packages and installs them using `opkg install` 185 | * **Temporary installation**: 186 | Downloads and extracts the ipk, places binaries in `/tmp`, 187 | and creates symlinks under `/usr/sbin` 188 | 189 | See [install_en.sh](../install_en.sh) for details. 190 | 191 | --- 192 | 193 | ### Special Thanks 🙏 194 | 195 | **[[UPX](https://upx.github.io/)]** 196 | Binary compression technology that makes ultra-small Tailscale builds possible 197 | 198 | **[[GitHub Actions](https://github.com/features/actions)]** 199 | Used for automated build and release 200 | 201 | **[[glinet-tailscale-updater](https://github.com/Admonstrator/glinet-tailscale-updater)]** 202 | One of the original technical references — highly recommended for GL.iNet devices 203 | 204 | **[[tailscale-openwrt](https://github.com/CH3NGYZ/tailscale-openwrt)]** 205 | Another early reference providing Tailscale install scripts for OpenWrt 206 | 207 | **[[openwrt-tailscale-repo](https://github.com/lanrat/openwrt-tailscale-repo)]** 208 | Feed repository reference 209 | 210 | --- 211 | 212 | ### Issue Reporting 213 | 214 | Please submit issues via 215 | [Issues](https://github.com/GuNanOvO/openwrt-tailscale/issues) 216 | and include: 217 | 218 | 1. Device architecture (`uname -m`) 219 | 2. Target platform architectures (`opkg print-architecture`) 220 | 3. Installation mode (persistent / temporary / opkg) 221 | 4. Relevant log snippets 222 | 223 | --- 224 | 225 | ### Forking This Project 226 | 227 | If you plan to fork this project, note the following: 228 | 229 | **Modify install script** 230 | 231 | * Update `REPO_URL` and `REPO` variables at the top of `install_en.sh` 232 | 233 | **Modify GitHub Actions workflows** 234 | 235 | * Replace all occurrences of `GuNanOvO/openwrt-tailscale` in 236 | `.github/workflows/build-tailscale.yml` and 237 | `.github/workflows/check-version.yml` 238 | (usually only the `env` section needs changes) 239 | 240 | **Secrets used in workflows** 241 | 242 | * `secrets.USIGN_SECRET_KEY_B64` 243 | 244 | * Base64-encoded usign private key for signing ipk packages 245 | * `secrets.PAT_TOKEN` 246 | 247 | * GitHub token with `repo` permission, used to trigger build workflow 248 | * `secrets.GHCR_READ_TOKEN` 249 | 250 | * GitHub token with `read:packages` permission 251 | * Used to detect upstream GHCR releases (optional) 252 | 253 | --- 254 | 255 | ### Security Statement 256 | 257 | This project redistributes the official open-source **Tailscale** software, 258 | aiming to provide OpenWrt users with **up-to-date**, **space-efficient** packages 259 | to replace outdated versions in official feeds. 260 | 261 | Outdated Tailscale versions may contain known security vulnerabilities. 262 | Timely updates are critical for network security. 263 | 264 | **Transparency & Verifiability** 265 | 266 | * **Open source**: All packaging, build, and install scripts are fully open 267 | * **Automated builds**: Entirely built by GitHub Actions with public logs 268 | * **Official source builds**: All binaries are compiled from official 269 | [**Tailscale**](https://github.com/tailscale/tailscale) release sources 270 | * **Reproducible**: Anyone can reproduce the build locally or via GitHub 271 | 272 | **Security Commitment** 273 | 274 | * No malicious code, no data collection, no telemetry injection 275 | * Only size/build optimizations, no changes to core functionality or security 276 | * All releases include verifiable build records and checksums 277 | (SHA256 / usign signatures) 278 | 279 | This project strives to provide OpenWrt users with a **secure, transparent, 280 | and auditable** way to install and update Tailscale. 281 | 282 | --- 283 | 284 | ### License 285 | 286 | This project is licensed under the **MIT License** 287 | and includes code from the 288 | [**Tailscale**](https://github.com/tailscale/tailscale) project, 289 | which is licensed under **BSD 3-Clause**. 290 | 291 | --- 292 | 293 | > 💖 If this project helps you, please consider giving it a ⭐! 294 | -------------------------------------------------------------------------------- /.github/workflows/check-version.yml: -------------------------------------------------------------------------------- 1 | name: Daily Tailscale Version Check 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | schedule: 8 | - cron: '0 4,8,18,22 * * *' 9 | workflow_dispatch: 10 | inputs: 11 | version_check_method: 12 | description: 'Version check method to use' 13 | type: choice 14 | options: 15 | - ghcr 16 | - tag 17 | default: 'ghcr' 18 | required: true 19 | 20 | env: 21 | REPO_SMALL: "GuNanOvO/openwrt-tailscale" 22 | REPO_SMALL_NAME: "openwrt-tailscale" 23 | REPO_SMALL_OWNER: "GuNanOvO" 24 | 25 | jobs: 26 | version-check: 27 | name: Check Version Differences 28 | runs-on: ubuntu-latest 29 | outputs: 30 | should_build: ${{ steps.compare.outputs.should_build }} 31 | selected_version: ${{ steps.compare.outputs.selected_version }} 32 | check_method: ${{ steps.compare.outputs.check_method }} 33 | steps: 34 | - name: Checkout repository 35 | uses: actions/checkout@v4 36 | 37 | - name: Load check method from .config/check-method 38 | id: load-method 39 | run: | 40 | # 优先使用 workflow_dispatch 输入,其次读取配置文件,最后默认 ghcr 41 | if [ -n "${{ github.event.inputs.version_check_method }}" ]; then 42 | method="${{ github.event.inputs.version_check_method }}" 43 | echo "Using manually provided method: $method" 44 | elif [ -f .config/check-method ]; then 45 | method=$(cat .config/check-method | tr -d '[:space:]') 46 | echo "Using method from .config/check-method: $method" 47 | else 48 | method="ghcr" 49 | echo "Using default method: $method" 50 | fi 51 | 52 | # 输出给后续步骤使用 53 | echo "method=$method" >> $GITHUB_OUTPUT 54 | 55 | - name: Get GHCR stable version 56 | id: get_ghcr 57 | uses: actions/github-script@v6 58 | env: 59 | GHCR_TOKEN: ${{ secrets.GHCR_READ_TOKEN }} 60 | with: 61 | script: | 62 | const token = process.env.GHCR_TOKEN; 63 | const res = await fetch( 64 | 'https://api.github.com/orgs/tailscale/packages/container/tailscale/versions', 65 | { headers: { 66 | 'Authorization': `Bearer ${token}`, 67 | 'Accept': 'application/vnd.github.v3+json' 68 | } 69 | } 70 | ); 71 | const versions = await res.json(); 72 | 73 | const latestStableObj = versions.find(v => { 74 | const tags = v.metadata.container.tags || []; 75 | return tags.includes('stable') && tags.includes('latest'); 76 | }); 77 | 78 | if (!latestStableObj) throw new Error('No version found with both stable and latest tags'); 79 | 80 | const versionTag = (latestStableObj.metadata.container.tags || []) 81 | .filter(tag => tag !== 'latest' && tag !== 'stable')[0]; 82 | 83 | if (!versionTag) throw new Error('No version tag found'); 84 | 85 | console.log('Upstream latest stable version tag:', versionTag); 86 | core.setOutput('stable_version', versionTag); 87 | 88 | - name: Get latest tag from release 89 | id: get_tag_release 90 | uses: actions/github-script@v6 91 | with: 92 | script: | 93 | const { data: tags } = await github.rest.repos.listTags({ 94 | owner: 'tailscale', 95 | repo: 'tailscale', 96 | per_page: 100 97 | }); 98 | 99 | const stableTags = tags 100 | .map(tag => tag.name) 101 | .filter(name => /^v\d+\.\d+\.\d+$/.test(name)); 102 | 103 | stableTags.sort((a, b) => { 104 | const parse = v => v.slice(1).split('.').map(Number); 105 | const [a1, a2, a3] = parse(a); 106 | const [b1, b2, b3] = parse(b); 107 | return b1 - a1 || b2 - a2 || b3 - a3; 108 | }); 109 | 110 | const latest = stableTags[0] || ''; 111 | core.setOutput('tag', latest); 112 | 113 | - name: Get latest release tag from openwrt-tailscale 114 | id: get_small 115 | uses: actions/github-script@v6 116 | with: 117 | script: | 118 | try { 119 | const { data } = await github.rest.repos.getLatestRelease({ 120 | owner: '${{ env.REPO_SMALL_OWNER }}', 121 | repo: '${{ env.REPO_SMALL_NAME }}' 122 | }); 123 | core.setOutput('tag', data?.tag_name || ''); 124 | } catch (error) { 125 | core.setOutput('tag', ''); 126 | } 127 | 128 | - name: Compare versions 129 | id: compare 130 | run: | 131 | check_method="${{ steps.load-method.outputs.method }}" 132 | echo "Using version check method: $check_method" 133 | 134 | if [ "$check_method" = "ghcr" ]; then 135 | echo "GHCR Upstream Version: ${{ steps.get_ghcr.outputs.stable_version }}" 136 | echo "Local Version: ${{ steps.get_small.outputs.tag }}" 137 | 138 | if [ "${{ steps.get_ghcr.outputs.stable_version }}" != "${{ steps.get_small.outputs.tag }}" ]; then 139 | echo "New version found in GHCR" 140 | echo "should_build=true" >> ${GITHUB_OUTPUT} 141 | echo "selected_version=${{ steps.get_ghcr.outputs.stable_version }}" >> ${GITHUB_OUTPUT} 142 | echo "check_method=ghcr" >> ${GITHUB_OUTPUT} 143 | else 144 | echo "No new version found in GHCR" 145 | echo "should_build=false" >> ${GITHUB_OUTPUT} 146 | fi 147 | else 148 | echo "Tag Release Version: ${{ steps.get_tag_release.outputs.tag }}" 149 | echo "Local Version: ${{ steps.get_small.outputs.tag }}" 150 | 151 | if [ "${{ steps.get_tag_release.outputs.tag }}" != "${{ steps.get_small.outputs.tag }}" ]; then 152 | echo "New version found in tag release" 153 | echo "should_build=true" >> ${GITHUB_OUTPUT} 154 | echo "selected_version=${{ steps.get_tag_release.outputs.tag }}" >> ${GITHUB_OUTPUT} 155 | echo "check_method=tag" >> ${GITHUB_OUTPUT} 156 | else 157 | echo "No new version found in tag release" 158 | echo "should_build=false" >> ${GITHUB_OUTPUT} 159 | fi 160 | fi 161 | 162 | 163 | trigger-build: 164 | name: Trigger Build Workflow with Commit 165 | permissions: 166 | contents: write 167 | needs: version-check 168 | if: needs.version-check.outputs.should_build == 'true' 169 | runs-on: ubuntu-latest 170 | outputs: 171 | ghcr_commit: ${{ steps.get_commit.outputs.commit }} 172 | steps: 173 | - name: Checkout repository 174 | uses: actions/checkout@v4 175 | with: 176 | fetch-depth: 0 177 | 178 | - name: Get commit based on selected method 179 | id: get_commit 180 | run: | 181 | check_method="${{ needs.version-check.outputs.check_method }}" 182 | version="${{ needs.version-check.outputs.selected_version }}" 183 | echo "Using method: $check_method with version: $version" 184 | 185 | if [ "$check_method" = "ghcr" ]; then 186 | echo "Getting commit from GHCR version ${version}" 187 | docker pull ghcr.io/tailscale/tailscale:${version} 188 | commit=$(docker run --rm ghcr.io/tailscale/tailscale:${version} tailscale version | grep -oP 'tailscale commit: \K\w+') 189 | if [ -n "$commit" ]; then 190 | echo "Commit from GHCR: $commit" 191 | echo "commit=$commit" >> $GITHUB_OUTPUT 192 | echo "version=$version" >> $GITHUB_OUTPUT 193 | exit 0 194 | fi 195 | else 196 | echo "Getting commit from tag release $version" 197 | git ls-remote https://github.com/tailscale/tailscale.git refs/tags/${version} | cut -f1 > tag_commit.txt 198 | commit=$(cat tag_commit.txt) 199 | if [ -n "$commit" ]; then 200 | echo "Commit from tag: $commit" 201 | echo "commit=$commit" >> $GITHUB_OUTPUT 202 | echo "version=$version" >> $GITHUB_OUTPUT 203 | exit 0 204 | fi 205 | fi 206 | 207 | echo "Failed to get commit using $check_method method" 208 | exit 1 209 | 210 | - name: Update Makefile with new version and hash 211 | id: update_makefile 212 | run: | 213 | RAW_VER="${{ steps.get_commit.outputs.version }}" 214 | NEW_VER=$(echo "$RAW_VER" | sed 's/^v//') 215 | NEW_COMMIT="${{ steps.get_commit.outputs.commit }}" 216 | 217 | SOURCE_URL="https://codeload.github.com/tailscale/tailscale/tar.gz/v${NEW_VER}?" 218 | echo "Downloading from $SOURCE_URL to calculate hash..." 219 | NEW_HASH=$(curl -L "$SOURCE_URL" | sha256sum | awk '{print $1}') 220 | echo "New Hash: $NEW_HASH" 221 | 222 | sed -i "s/^PKG_VERSION:=.*/PKG_VERSION:=$NEW_VER/" package/tailscale/Makefile 223 | sed -i "s/^PKG_HASH:=.*/PKG_HASH:=$NEW_HASH/" package/tailscale/Makefile 224 | sed -i "s/PKG_COMMIT:=.*/PKG_COMMIT:=$NEW_COMMIT/" package/tailscale/Makefile 225 | 226 | - name: Commit and push changes 227 | run: | 228 | git config --local user.name "github-actions[bot]" 229 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 230 | git add package/tailscale/Makefile 231 | git commit -m "chore: update tailscale to ${{ steps.get_commit.outputs.version}} (${{ steps.get_commit.outputs.commit }})" || true 232 | git push origin main 233 | 234 | - name: Trigger build workflow 235 | uses: peter-evans/repository-dispatch@v4 236 | with: 237 | event-type: build-tailscale 238 | token: ${{ secrets.PAT_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/build-tailscale.yml: -------------------------------------------------------------------------------- 1 | name: Smaller Tailscale Build Pipeline 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | paths: 8 | - version 9 | repository_dispatch: 10 | types: [ build-tailscale ] 11 | 12 | permissions: 13 | contents: read 14 | 15 | env: 16 | SOFTWARE_NAME: "Tailscale" 17 | FILE_NAME: "tailscaled" 18 | REPO: "tailscale/tailscale" 19 | REPO_SMALL: "GuNanOvO/openwrt-tailscale" 20 | REPO_SMALL_NAME: "openwrt-tailscale" 21 | REPO_SMALL_OWNER: "GuNanOvO" 22 | ARTIFACT_DIR: "artifacts" 23 | BUILD_DATE: "" 24 | 25 | jobs: 26 | build: 27 | name: Build for ${{ matrix.platform }} 28 | runs-on: ubuntu-latest 29 | outputs: 30 | version: ${{ env.PKG_VERSION }} 31 | date: ${{ env.BUILD_DATE }} 32 | strategy: 33 | matrix: 34 | sdk: 35 | - 24.10.4 36 | platform: 37 | - 'aarch64_cortex-a53' 38 | - 'aarch64_cortex-a72' 39 | - 'aarch64_cortex-a76' 40 | - 'aarch64_generic' 41 | - 'arm_arm1176jzf-s_vfp' 42 | - 'arm_arm926ej-s' 43 | - 'arm_cortex-a15_neon-vfpv4' 44 | - 'arm_cortex-a5_vfpv4' 45 | - 'arm_cortex-a7' 46 | - 'arm_cortex-a7_neon-vfpv4' 47 | - 'arm_cortex-a7_vfpv4' 48 | - 'arm_cortex-a8_vfpv3' 49 | - 'arm_cortex-a9' 50 | - 'arm_cortex-a9_neon' 51 | - 'arm_cortex-a9_vfpv3-d16' 52 | - 'arm_fa526' 53 | - 'arm_xscale' 54 | - 'i386_pentium-mmx' 55 | - 'i386_pentium4' 56 | - 'loongarch64_generic' 57 | - 'mips64_mips64r2' 58 | - 'mips64_octeonplus' 59 | - 'mips64el_mips64r2' 60 | - 'mips_24kc' 61 | - 'mips_4kec' 62 | - 'mips_mips32' 63 | - 'mipsel_24kc' 64 | - 'mipsel_24kc_24kf' 65 | - 'mipsel_74kc' 66 | - 'mipsel_mips32' 67 | - 'riscv64_riscv64' 68 | - 'x86_64' 69 | fail-fast: true 70 | 71 | steps: 72 | 73 | - name: Set build timestamp 74 | run: | 75 | echo "BUILD_DATE=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_ENV 76 | 77 | - name: Checkout repository (develop branch) 78 | uses: actions/checkout@v4 79 | with: 80 | ref: main 81 | path: repo 82 | 83 | - name: Prepare package 84 | run: | 85 | VERSION=$(sed -n 's/^PKG_VERSION:=\(.*\)/\1/p' repo/package/tailscale/Makefile | tr -d '[:space:]') 86 | RELEASE=$(sed -n 's/^PKG_COMMIT:=\(.*\)/\1/p' repo/package/tailscale/Makefile | tr -d '[:space:]') 87 | 88 | echo "PKG_VERSION=$VERSION" >> $GITHUB_ENV 89 | echo "PKG_COMMIT=$RELEASE" >> $GITHUB_ENV 90 | 91 | mkdir tailscale 92 | cp -r repo/package/tailscale/. tailscale/ 93 | 94 | echo "Parsed version: $VERSION" 95 | echo "Parsed release: $RELEASE" 96 | 97 | # UPX 98 | 99 | - name: Check UPX Latest Version 100 | id: check_upx 101 | run: | 102 | UPX_TAG=$(curl -s https://api.github.com/repos/upx/upx/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 103 | UPX_VER="${UPX_TAG#v}" 104 | echo "latest_tag=$UPX_TAG" >> $GITHUB_OUTPUT 105 | echo "latest_ver=$UPX_VER" >> $GITHUB_OUTPUT 106 | echo "Detected UPX Version: $UPX_TAG" 107 | 108 | - name: Cache UPX 109 | id: cache-upx 110 | uses: actions/cache@v4 111 | with: 112 | path: upx 113 | key: upx-linux-amd64-${{ steps.check_upx.outputs.latest_tag }} 114 | 115 | - name: Prepare UPX 116 | if: steps.cache-upx.outputs.cache-hit != 'true' 117 | env: 118 | UPX_TAG: ${{ steps.check_upx.outputs.latest_tag }} 119 | UPX_VER: ${{ steps.check_upx.outputs.latest_ver }} 120 | run: | 121 | UPX_URL="https://github.com/upx/upx/releases/download/${UPX_TAG}/upx-${UPX_VER}-amd64_linux.tar.xz" 122 | mkdir -p upx 123 | curl -L -o upx.tar.xz "$UPX_URL" 124 | tar -xf upx.tar.xz -C upx --strip-components=1 125 | chmod +x upx/upx 126 | 127 | - name: Verify UPX 128 | run: | 129 | ./upx/upx --version 130 | echo "UPX_BIN_PATH=$(pwd)/upx" >> $GITHUB_ENV 131 | 132 | # GO 133 | 134 | - name: Check Go Latest Version 135 | id: check_go 136 | run: | 137 | GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | head -1) 138 | echo "latest_version=$GO_VERSION" >> $GITHUB_OUTPUT 139 | echo "Detected Go Version: $GO_VERSION" 140 | 141 | - name: Cache Go 142 | id: cache-go 143 | uses: actions/cache@v4 144 | with: 145 | path: go 146 | key: go-linux-amd64-${{ steps.check_go.outputs.latest_version }} 147 | 148 | - name: Prepare Go 149 | if: steps.cache-go.outputs.cache-hit != 'true' 150 | env: 151 | GO_VERSION: ${{ steps.check_go.outputs.latest_version }} 152 | GO_ARCH: amd64 153 | GO_OS: linux 154 | run: | 155 | GO_URL="https://go.dev/dl/${GO_VERSION}.${GO_OS}-${GO_ARCH}.tar.gz" 156 | mkdir -p go 157 | curl -L -o go.tar.gz "$GO_URL" 158 | tar -C go -xzf go.tar.gz --strip-components=1 159 | rm go.tar.gz 160 | 161 | - name: Verify Go 162 | run: | 163 | ./go/bin/go version 164 | echo "GO_BIN_PATH=$(pwd)/go" >> $GITHUB_ENV 165 | 166 | - name: Pull OpenWRT SDK Docker image 167 | run: | 168 | docker pull ghcr.io/openwrt/sdk:${{ matrix.platform }}-V${{ matrix.sdk }} 169 | 170 | # BUILD 171 | 172 | - name: Prepare build 173 | run: | 174 | mkdir -p bin 175 | mkdir -p "${{ env.ARTIFACT_DIR }}" 176 | chmod -R 777 bin || true 177 | ls -la 178 | 179 | - name: Build with OpenWRT SDK Docker 180 | run: | 181 | cat > build_script.sh << 'BUILD_SCRIPT_END' 182 | #!/bin/bash 183 | set -e 184 | cd /builder 185 | 186 | ./scripts/feeds update packages > /dev/null 187 | ./scripts/feeds install golang > /dev/null 188 | 189 | mkdir -p /builder/package/lang 190 | # ln -sf /builder/feeds/packages/lang/golang /builder/package/lang/golang 191 | 192 | rm -rf /builder/package/tailscale 193 | mkdir -p /builder/package/tailscale 194 | cp -r /builder/tailscale/. /builder/package/tailscale/ 195 | 196 | make defconfig > /dev/null 2>&1 197 | 198 | [ -f /builder/go/bin/go ] || { echo "Error: Go binary missing"; exit 1; } 199 | 200 | mkdir -p /builder/staging_dir/hostpkg/bin 201 | ln -sf /builder/go/bin/go /builder/staging_dir/hostpkg/bin/go 202 | ln -sf /builder/go/bin/gofmt /builder/staging_dir/hostpkg/bin/gofmt 203 | 204 | if [ -d /builder/staging_dir/hostpkg/lib/go-cross/bin ]; then 205 | ln -sf /builder/go/bin/go /builder/staging_dir/hostpkg/lib/go-cross/bin/go 206 | fi 207 | 208 | echo "Using $(/builder/go/bin/go version)" 209 | 210 | make package/tailscale/compile V=s 211 | 212 | IPKS=$(find bin/packages bin/targets -name "tailscale_*.ipk" -type f) 213 | if [ -n "$IPKS" ]; then 214 | echo "Build Success:" 215 | ls -lh $IPKS 216 | else 217 | echo "Error: No IPK found" && exit 1 218 | fi 219 | BUILD_SCRIPT_END 220 | 221 | docker run --rm \ 222 | -v "$(pwd)/bin:/builder/bin:z" \ 223 | -v "$(pwd)/tailscale:/builder/tailscale:ro,z" \ 224 | -v "${{ env.UPX_BIN_PATH }}:/builder/upx:ro,z" \ 225 | -v "${{ env.GO_BIN_PATH }}:/builder/go:ro,z" \ 226 | -v "$(pwd)/build_script.sh:/build_script.sh:ro,z" \ 227 | -e TERM=xterm \ 228 | -e FORCE=1 \ 229 | ghcr.io/openwrt/sdk:${{ matrix.platform }}-V${{ matrix.sdk }} \ 230 | bash /build_script.sh 231 | 232 | # UPLOAD 233 | 234 | - name: Collect build artifacts 235 | shell: bash 236 | run: | 237 | mkdir -p "${{ env.ARTIFACT_DIR }}" 238 | 239 | echo "Searching for IPK for platform: ${{ matrix.platform }}..." 240 | 241 | if [ -d "bin/packages/${{ matrix.platform }}" ]; then 242 | mapfile -t ipk_files < <(find "bin/packages/${{ matrix.platform }}" -name "tailscale_*.ipk" -type f) 243 | else 244 | mapfile -t ipk_files < <(find bin -name "tailscale_*.ipk" -type f) 245 | fi 246 | 247 | if [ ${#ipk_files[@]} -eq 0 ]; then 248 | echo "::error::No IPK files found for ${{ matrix.platform }}!" 249 | exit 1 250 | fi 251 | 252 | source_ipk=$(ls -t "${ipk_files[@]}" | head -n 1) 253 | 254 | target_ipk="${{ env.ARTIFACT_DIR }}/tailscale_${{ env.PKG_VERSION }}_${{ matrix.platform }}.ipk" 255 | 256 | echo "Found IPK: $source_ipk" 257 | echo "Collecting to: $target_ipk" 258 | 259 | cp -v "$source_ipk" "$target_ipk" 260 | 261 | if [ ! -s "$target_ipk" ]; then 262 | echo "::error::Artifact collection failed: $target_ipk is missing or empty!" 263 | exit 1 264 | fi 265 | 266 | echo "Success: Artifact ready in ${{ env.ARTIFACT_DIR }}" 267 | ls -lh "${{ env.ARTIFACT_DIR }}" 268 | 269 | - name: Upload artifact 270 | uses: actions/upload-artifact@v4 271 | with: 272 | name: tailscale_${{ env.PKG_VERSION }}_${{ matrix.platform }} 273 | path: ${{ env.ARTIFACT_DIR }}/ 274 | if-no-files-found: error 275 | retention-days: 30 276 | 277 | release: 278 | name: Create Release 279 | needs: [build] 280 | runs-on: ubuntu-latest 281 | permissions: 282 | contents: write 283 | env: 284 | RELEASE_TAG: v${{ needs.build.outputs.version }} 285 | VERSION: ${{ needs.build.outputs.version }} 286 | BUILD_DATE: ${{ needs.build.outputs.date }} 287 | ARTIFACT_DIR: "release_assets" 288 | steps: 289 | - name: Prepare workspace 290 | run: mkdir -p ${{ env.ARTIFACT_DIR }} 291 | 292 | - name: Download artifacts 293 | uses: actions/download-artifact@v4 294 | with: 295 | path: temp_artifacts 296 | 297 | - name: Organize and Generate Files 298 | run: | 299 | JSON_FILE="${{ env.ARTIFACT_DIR }}/artifacts.json" 300 | PACKAGES_FILE="${{ env.ARTIFACT_DIR }}/Packages" 301 | 302 | echo "[" > "$JSON_FILE" 303 | : > "$PACKAGES_FILE" # 使用 : > 清空文件更简洁 304 | 305 | first=true 306 | # 遍历所有下载的 artifacts 目录 307 | for dir in temp_artifacts/*/; do 308 | [ -d "$dir" ] || continue 309 | 310 | ipk_path=$(find "$dir" -name "tailscale_*.ipk" -type f | head -n 1) 311 | [ -z "$ipk_path" ] && continue 312 | 313 | ipk_name=$(basename "$ipk_path") 314 | 315 | # 改进后的架构提取逻辑:提取第3个下划线之后的部分 316 | arch=$(echo "$ipk_name" | cut -d'_' -f3- | sed 's/\.ipk$//') 317 | 318 | # 复制 IPK 到发布目录 319 | cp "$ipk_path" "${{ env.ARTIFACT_DIR }}/" 320 | 321 | echo "Processing $ipk_name for $arch..." 322 | 323 | # 提取 control 内容 324 | CONTROL_CONTENT=$(tar -xOzf "$ipk_path" ./control.tar.gz | tar -xOzf - ./control 2>/dev/null || \ 325 | tar -xOzf "$ipk_path" control.tar.gz | tar -xOzf - control) 326 | # 计算文件大小和 SHA256 327 | FILE_SIZE=$(stat -c%s "$ipk_path") 328 | FILE_SHA256=$(sha256sum "$ipk_path" | awk '{print $1}') 329 | 330 | # 组合写入 Packages 331 | # 使用 sed 确保每个 control 段落后都有换行符,避免字段粘连 332 | { 333 | echo "$CONTROL_CONTENT" | sed '$a\' 334 | echo "Filename: $ipk_name" 335 | echo "Size: $FILE_SIZE" 336 | echo "SHA256sum: $FILE_SHA256" 337 | echo "" 338 | } >> "$PACKAGES_FILE" 339 | 340 | # 写入 JSON 341 | if [ "$first" = true ]; then 342 | first=false 343 | else 344 | echo "," >> "$JSON_FILE" 345 | fi 346 | echo "{\"target\":\"${arch}\",\"ipk\":\"${ipk_name}\",\"sha256\":\"${FILE_SHA256}\"}" >> "$JSON_FILE" 347 | done 348 | 349 | echo "]" >> "$JSON_FILE" 350 | 351 | echo "${{ env.VERSION }}" > "${{ env.ARTIFACT_DIR }}/version" 352 | 353 | # 生成 Packages.gz 354 | gzip -kf "$PACKAGES_FILE" 355 | 356 | - name: Checkout feed branch 357 | uses: actions/checkout@v4 358 | with: 359 | ref: feed 360 | path: feed 361 | 362 | - name: Setup Usign 363 | run: | 364 | git clone --depth 1 https://git.openwrt.org/project/usign.git 365 | cd usign && mkdir build && cd build 366 | cmake .. && make -j$(nproc) && sudo make install 367 | 368 | - name: Deploy to Feed Branch 369 | env: 370 | USIGN_SECRET_KEY_B64: ${{ secrets.USIGN_SECRET_KEY_B64 }} 371 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 372 | run: | 373 | # 清理旧的 tailscale 相关文件 374 | git rm -rf feed/tailscale_*.ipk feed/Packages* 2>/dev/null || true 375 | 376 | # 拷贝新生成的文件 377 | cp ${{ env.ARTIFACT_DIR }}/tailscale_*.ipk feed/ 378 | cp ${{ env.ARTIFACT_DIR }}/Packages feed/ 379 | cp ${{ env.ARTIFACT_DIR }}/Packages.gz feed/ 380 | 381 | # 签名逻辑 382 | echo "$USIGN_SECRET_KEY_B64" | base64 -d > key-build.sec 383 | usign -S -m feed/Packages -s key-build.sec -x feed/Packages.sig 384 | rm -f key-build.sec 385 | 386 | generate_tbody() { 387 | echo " " 388 | for file in feed/Packages* feed/tailscale*; do 389 | [ -f "${file}" ] || continue 390 | fname=$(basename "${file}") 391 | fsize=$(du -h "${file}" | cut -f1) 392 | fdate=$(date -r "${file}" +"%Y-%m-%d %a %H:%M:%S UTC") 393 | echo " " 394 | echo " $fname" 395 | echo " $fsize" 396 | echo " $fdate" 397 | echo " " 398 | done 399 | echo " " 400 | } 401 | 402 | 403 | generate_tbody > feed/tbody.tmp 404 | sed -i '//,// { 405 | //!{ 406 | //!d 407 | } 408 | }' feed/index.html 409 | sed -i '//r feed/tbody.tmp' feed/index.html 410 | 411 | current_date="${{ env.BUILD_DATE }}" 412 | sed -i '//,// { 413 | // { 414 | s#.*#'"${current_date}"'# 415 | } 416 | }' feed/index.html 417 | 418 | version=$(echo "${{ env.RELEASE_TAG }}" | sed 's/^v//') 419 | sed -i '//,// { 420 | // { 421 | s#.*#'"${version}"'# 422 | } 423 | }' feed/index.html 424 | 425 | rm -f feed/tbody.tmp 426 | 427 | - name: Commit and push to feed branch 428 | run: | 429 | git config user.name "github-actions[bot]" 430 | git config user.email "github-actions[bot]@users.noreply.github.com" 431 | git add -A 432 | git diff --cached --quiet && echo "No changes" && exit 0 433 | git commit -m "Update feed: ${{ env.BUILD_DATE }}" 434 | git push origin feed --force 435 | working-directory: feed 436 | 437 | - name: Generate release markdown 438 | id: generate_release_markdown 439 | run: | 440 | # 读取 artifacts.json 并生成简单的 markdown 表格 441 | artifacts_file="${{ env.ARTIFACT_DIR }}/artifacts.json" 442 | if [[ ! -f "${artifacts_file}" ]]; then 443 | echo "Error: artifacts.json missing" 444 | exit 1 445 | fi 446 | 447 | owner="${{ env.REPO_SMALL_OWNER }}" 448 | repo="${{ env.REPO_SMALL_NAME }}" 449 | tag="${{ env.RELEASE_TAG }}" 450 | 451 | # 构建 Markdown 表格 452 | table="| Target Platform / 目标平台 | IPK Package (Direct Link) |\n|:--- |:--- |" 453 | rows=$(jq -r --arg owner "$owner" --arg repo "$repo" --arg tag "$tag" ' 454 | .[] | "| \(.target) | [\(.ipk)](https://github.com/\($owner)/\($repo)/releases/download/\($tag)/\(.ipk)) |" 455 | ' ${artifacts_file}) 456 | full_table="${table}\n${rows}" 457 | 458 | # 设置 Release Body 459 | release_body=" 460 | ## Smaller Tailscale / 更小的Tailscale 461 | #### Version / 版本 : ${{ env.RELEASE_TAG }} 462 | #### Build Date / 构建日期 : ${{ env.BUILD_DATE }} 463 | #### Upstream Changelog / 上游更新日志 : [https://tailscale.com/changelog](https://tailscale.com/changelog) 464 | --- 465 | 466 | #### 本项目是为 OpenWRT 构建的 Tailscale 精简版本,使用官方 SDK 编译,旨在降低存储空间占用。 467 | - 使用 OpenWRT 官方 SDK 编译。 468 | - 采用 UPX 压缩技术大幅缩小二进制体积。 469 | - 标准 IPK 格式,支持 opkg 直接安装。 470 | 471 | #### Minimalistic Tailscale build for OpenWRT using the official SDK. 472 | - Compiled with official OpenWRT SDK. 473 | - Significant size reduction via UPX compression. 474 | - Standard IPK format for direct opkg installation. 475 | 476 | #### Architecture Check / 架构查询 477 | Run the following command on your device to confirm target platform: 478 | \`\`\`bash 479 | opkg print-architecture 480 | \`\`\` 481 | 482 | --- 483 | 484 | #### Download Links / 下载清单 485 | > [!IMPORTANT] 486 | > 请根据设备架构选择对应的安装包。 487 | > Please select the correct package for your device architecture. 488 | 489 |
490 | Package List / 文件列表 491 | 492 | ${full_table} 493 | 494 |
495 | " 496 | 497 | echo "release_body<> $GITHUB_OUTPUT 498 | echo -e "${release_body}" >> $GITHUB_OUTPUT 499 | echo "EOF" >> $GITHUB_OUTPUT 500 | 501 | - name: Create GitHub Release 502 | uses: softprops/action-gh-release@v1 503 | with: 504 | tag_name: ${{ env.RELEASE_TAG }} 505 | name: "Smaller ${{ env.SOFTWARE_NAME }} ${{ env.RELEASE_TAG }}" 506 | body: ${{ steps.generate_release_markdown.outputs.release_body }} 507 | files: | 508 | ${{ env.ARTIFACT_DIR }}/*.ipk 509 | ${{ env.ARTIFACT_DIR }}/Packages 510 | ${{ env.ARTIFACT_DIR }}/Packages.gz 511 | ${{ env.ARTIFACT_DIR }}/artifacts.json 512 | ${{ env.ARTIFACT_DIR }}/version 513 | 514 | - name: Sync to small repo 515 | uses: actions/github-script@v6 516 | with: 517 | script: | 518 | await github.rest.git.createTag({ 519 | owner: '${{ env.REPO_SMALL_OWNER }}', 520 | repo: '${{ env.REPO_SMALL_NAME }}', 521 | tag: '${{ env.RELEASE_TAG }}', 522 | message: 'Release ${{ env.RELEASE_TAG }}', 523 | object: '${{ github.sha }}', 524 | type: 'commit' 525 | }) -------------------------------------------------------------------------------- /install_en.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Script Information 4 | SCRIPT_VERSION="v1.08" 5 | SCRIPT_DATE="2025/12/15" 6 | 7 | # Basic Configuration 8 | REPO_URL="https://github.com/GuNanOvO/openwrt-tailscale" 9 | REPO="gunanovo/openwrt-tailscale" 10 | TAILSCALE_URL="${REPO_URL}/releases/latest" 11 | TAILSCALE_FILE="" # Set by get_tailscale_info 12 | PACKAGES_TO_CHECK="libc kmod-tun ca-bundle" 13 | 14 | # TMP Installation [/usr/sbin/tailscale] 15 | TMP_TAILSCALE='#!/bin/sh 16 | set -e 17 | 18 | if [ -f "/tmp/tailscale" ]; then 19 | /tmp/tailscale "$@" 20 | fi' 21 | # TMP Installation [/usr/sbin/tailscaled] 22 | TMP_TAILSCALED='#!/bin/sh 23 | set -e 24 | if [ -f "/tmp/tailscaled" ]; then 25 | /tmp/tailscaled "$@" 26 | else 27 | /usr/sbin/install.sh --tempinstall 28 | /tmp/tailscaled "$@" 29 | fi' 30 | 31 | TAILSCALE_LATEST_VERSION="" # Set by get_tailscale_info 32 | TAILSCALE_LOCAL_VERSION="" 33 | IS_TAILSCALE_INSTALLED="false" 34 | TAILSCALE_INSTALL_STATUS="none" 35 | FOUND_TAILSCALE_FILE="false" 36 | 37 | DEVICE_TARGET="" 38 | DEVICE_MEM_TOTAL="" 39 | DEVICE_MEM_FREE="" 40 | DEVICE_STORAGE_TOTAL="" 41 | DEVICE_STORAGE_AVAILABLE="" 42 | TAILSCALE_FILE_SIZE="" # Set by get_tailscale_info 43 | 44 | TAILSCALE_PERSISTENT_INSTALLABLE="" 45 | TAILSCALE_TEMP_INSTALLABLE="" 46 | 47 | ENABLE_INIT_PROGRESS_BAR="true" 48 | 49 | 50 | # Function: Script Information 51 | script_info() { 52 | echo "#╔╦╗┌─┐ ┬ ┬ ┌─┐┌─┐┌─┐┬ ┌─┐ ┌─┐┌┐┌ ╔═╗┌─┐┌─┐┌┐┌ ╦ ╦ ┬─┐┌┬┐ ╦ ┌┐┌┌─┐┌┬┐┌─┐┬ ┬ ┌─┐┬─┐#" 53 | echo "# ║ ├─┤ │ │ └─┐│ ├─┤│ ├┤ │ ││││ ║ ║├─┘├┤ │││ ║║║ ├┬┘ │ ║ │││└─┐ │ ├─┤│ │ ├┤ ├┬┘#" 54 | echo "# ╩ ┴ ┴ ┴ ┴─┘└─┘└─┘┴ ┴┴─┘└─┘ └─┘┘└┘ ╚═╝┴ └─┘┘└┘ ╚╩╝ ┴└─ ┴ ╩ ┘└┘└─┘ ┴ ┴ ┴┴─┘┴─┘└─┘┴└─#" 55 | echo "┌────────────────────────────────────────────────────────────────────────────────────────┐" 56 | echo "│ A script for installing Tailscale on OpenWrt, updating Tailscale, or... │" 57 | echo "│ Project URL: $REPO_URL │" 58 | echo "│ Script Version: $SCRIPT_VERSION │" 59 | echo "│ Update Date: $SCRIPT_DATE │" 60 | echo "│ Thank you for using, if it helps, please give a star /<3 │" 61 | echo "└────────────────────────────────────────────────────────────────────────────────────────┘" 62 | } 63 | 64 | # Function: Get Device Architecture 65 | check_device_target() { 66 | local exclude_target="powerpc_64_e5500|powerpc_464fp|powerpc_8548|armeb_xscale" 67 | local raw_target="" 68 | 69 | raw_target=$(opkg print-architecture 2>/dev/null | awk '{print $2}' | grep -vE "all|noarch" | head -n 1) 70 | 71 | if [ -z "$raw_target" ]; then 72 | raw_target=$(grep "DISTRIB_ARCH" /etc/openwrt_release 2>/dev/null | awk -F"'" '{print $2}') 73 | fi 74 | 75 | if [ -z "$raw_target" ]; then 76 | echo "[ERROR]: Unable to get device architecture, script exiting." 77 | exit 1 78 | fi 79 | 80 | if echo "$raw_target" | grep -iqE "$exclude_target"; then 81 | echo "[ERROR]: Current architecture [$raw_target] is in the exclusion list, script exiting." 82 | exit 1 83 | fi 84 | 85 | DEVICE_TARGET=$(echo "$raw_target" | tr -d '[:space:]') 86 | } 87 | 88 | # Function: Detect Tailscale Installation Status 89 | check_tailscale_install_status() { 90 | local bin_bin="/usr/bin/tailscaled" 91 | local bin_sbin="/usr/sbin/tailscaled" 92 | local bin_tmp="/tmp/tailscaled" 93 | 94 | local has_bin=false 95 | local has_sbin=false 96 | local has_tmp=false 97 | local bin_is_script=false 98 | 99 | [ -f "$bin_bin" ] && has_bin=true 100 | [ -f "$bin_sbin" ] && has_sbin=true 101 | [ -f "$bin_tmp" ] && has_tmp=true 102 | 103 | if $has_bin; then 104 | if head -n 1 "$bin_bin" 2>/dev/null | grep -q "^#!"; then 105 | bin_is_script=true 106 | fi 107 | fi 108 | 109 | if $has_sbin; then 110 | if head -n 1 "$bin_sbin" 2>/dev/null | grep -q "^#!"; then 111 | bin_is_script=true 112 | fi 113 | fi 114 | 115 | if command -v tailscale >/dev/null 2>&1; then 116 | local version_output 117 | version_output=$(tailscale version 2>/dev/null | head -n 1 | tr -d '[:space:]') 118 | [ -n "$version_output" ] && TAILSCALE_LOCAL_VERSION="$version_output" 119 | fi 120 | 121 | # Flexible Status Judgment 122 | if $has_tmp; then 123 | if $bin_is_script; then 124 | # Core scenario: binary in tmp, usr has boot script 125 | TAILSCALE_INSTALL_STATUS="temp" 126 | IS_TAILSCALE_INSTALLED="true" 127 | elif $has_bin || $has_sbin; then 128 | # Conflict scenario: tmp has, usr also has real binary 129 | TAILSCALE_INSTALL_STATUS="unknown" 130 | IS_TAILSCALE_INSTALLED="true" 131 | else 132 | # Pure temporary scenario: only tmp has 133 | TAILSCALE_INSTALL_STATUS="temp" 134 | IS_TAILSCALE_INSTALLED="true" 135 | fi 136 | elif $has_bin || $has_sbin; then 137 | # Persistent scenario: file in usr/sbin 138 | TAILSCALE_INSTALL_STATUS="persistent" 139 | IS_TAILSCALE_INSTALLED="true" 140 | else 141 | IS_TAILSCALE_INSTALLED="false" 142 | fi 143 | 144 | [ "$IS_TAILSCALE_INSTALLED" = "true" ] && FOUND_TAILSCALE_FILE="true" 145 | } 146 | 147 | # Function: Check Device Memory 148 | check_device_memory() { 149 | local mem_info=$(free 2>/dev/null | grep "Mem:") 150 | local mem_total_kb=$(echo "$mem_info" | awk '{print $2}') 151 | local mem_available_kb=$(echo "$mem_info" | awk '{print $7}') 152 | 153 | [ -z "$mem_available_kb" ] && mem_available_kb=$(echo "$mem_info" | awk '{print $4}') 154 | 155 | if [ -z "$mem_total_kb" ] || ! echo "$mem_total_kb" | grep -q '^[0-9]\+$'; then 156 | echo "[ERROR]: Unable to identify total device memory value" && exit 1 157 | fi 158 | 159 | if [ -z "$mem_available_kb" ] || ! echo "$mem_available_kb" | grep -q '^[0-9]\+$'; then 160 | echo "[ERROR]: Unable to identify available device memory value" && exit 1 161 | fi 162 | 163 | DEVICE_MEM_TOTAL=$((mem_total_kb / 1024)) 164 | DEVICE_MEM_FREE=$((mem_available_kb / 1024)) 165 | } 166 | 167 | # Function: Check Device Storage Space 168 | check_device_storage() { 169 | local mount_point="${1:-/}" 170 | 171 | local storage_info=$(df -Pk "$mount_point") 172 | local storage_used_kb=$(echo "$storage_info" | awk 'NR==2 {print $(NF-3)}') 173 | local storage_available_kb=$(echo "$storage_info" | awk 'NR==2 {print $(NF-2)}') 174 | 175 | if [ -z "$storage_used_kb" ] || ! echo "$storage_used_kb" | grep -q '^[0-9]\+$'; then 176 | echo "[ERROR]: Unable to identify used storage space value for $mount_point" && exit 1 177 | fi 178 | 179 | if ! echo "$storage_available_kb" | grep -q '^[0-9]\+$'; then 180 | echo "[ERROR]: Unable to identify available storage space value for $mount_point" && exit 1 181 | fi 182 | 183 | DEVICE_STORAGE_TOTAL=$(( (storage_used_kb + storage_available_kb) / 1024 )) 184 | DEVICE_STORAGE_AVAILABLE=$((storage_available_kb / 1024)) 185 | } 186 | 187 | # Function: Get Tailscale Information 188 | get_tailscale_info() { 189 | local version 190 | local file 191 | local file_size 192 | local tmp_packages="/tmp/Packages" 193 | # Try 3 times 194 | local attempt_range="1 2 3" 195 | # Timeout (seconds) 196 | local attempt_timeout=10 197 | 198 | for attempt_times in $attempt_range; do 199 | version=$(wget -qO- --timeout=$attempt_timeout "${TAILSCALE_URL}/download/version" | tr -d ' \n\r') 200 | file="tailscale_${version}_${DEVICE_TARGET}" 201 | 202 | wget -q --timeout=$attempt_timeout "${TAILSCALE_URL}/download/Packages" -O "$tmp_packages" 203 | file_size=$(awk -v ipk="${file}.ipk" ' 204 | BEGIN { RS=""; FS="\n" } 205 | $0 ~ ipk { 206 | for(i=1;i<=NF;i++) if($i ~ /^Installed-Size:/) { 207 | print $i; exit 208 | } 209 | }' "$tmp_packages" | awk '{print $2}') 210 | 211 | if [ -n "$version" ] && [ -n "$file_size" ]; then 212 | break 213 | else 214 | sleep 1 215 | fi 216 | done 217 | 218 | if [ -z "$version" ] || [ -z "$file_size" ]; then 219 | echo "" 220 | echo "[ERROR]: Unable to get tailscale version or file size" 221 | echo "1. Ensure network connection is normal" 222 | echo "2. Retry" 223 | echo "3. Report to developer" 224 | exit 1 225 | fi 226 | 227 | TAILSCALE_LATEST_VERSION="$version" 228 | TAILSCALE_FILE="$file" 229 | TAILSCALE_FILE_SIZE=$((file_size / 1024 / 1024)) 230 | 231 | if [ "$DEVICE_STORAGE_AVAILABLE" -gt "$TAILSCALE_FILE_SIZE" ]; then 232 | TAILSCALE_PERSISTENT_INSTALLABLE="true" 233 | else 234 | TAILSCALE_PERSISTENT_INSTALLABLE="false" 235 | fi 236 | 237 | if [ "$DEVICE_MEM_FREE" -gt "$TAILSCALE_FILE_SIZE" ]; then 238 | TAILSCALE_TEMP_INSTALLABLE="true" 239 | else 240 | TAILSCALE_TEMP_INSTALLABLE="false" 241 | fi 242 | } 243 | 244 | # Function: Update 245 | update() { 246 | echo "[INFO]: Updating..." 247 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 248 | temp_install "" "true" 249 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 250 | persistent_install "" "true" 251 | fi 252 | while true; do 253 | echo "╔═══════════════════════════════════════════════════════╗" 254 | echo "║ [WARNING]!!! Please confirm the following: ║" 255 | echo "║ ║" 256 | echo "║ You are updating Tailscale, Tailscale needs restart. ║" 257 | echo "║ If you are currently connected to the device via ║" 258 | echo "║ Tailscale, you may lose connection. Please confirm ║" 259 | echo "║ your operation to avoid loss! Thank you for using! ║" 260 | echo "║ ║" 261 | echo "╚═══════════════════════════════════════════════════════╝" 262 | 263 | read -n 1 -p "Confirm restart tailscale? (y/N): " choice 264 | 265 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 266 | /etc/init.d/tailscale stop 267 | /etc/init.d/tailscale start 268 | break 269 | else 270 | echo "[INFO]: Cancel restart tailscale, you can restart tailscale service later with command: /etc/init.d/tailscale stop && /etc/init.d/tailscale start" 271 | break 272 | fi 273 | done 274 | } 275 | 276 | # Function: Uninstall 277 | remove() { 278 | while true; do 279 | echo "╔═══════════════════════════════════════════════════════╗" 280 | echo "║ [WARNING]!!! Please confirm the following: ║" 281 | echo "║ ║" 282 | echo "║ You are uninstalling Tailscale. After uninstallation, ║" 283 | echo "║ all your services relying on Tailscale will fail. If ║" 284 | echo "║ you are currently connected to the device via ║" 285 | echo "║ Tailscale, you may lose connection. Please confirm ║" 286 | echo "║ your operation to avoid loss! Thank you for using! ║" 287 | echo "║ ║" 288 | echo "╚═══════════════════════════════════════════════════════╝" 289 | 290 | read -n 1 -p "Confirm uninstall tailscale? (y/N): " choice 291 | 292 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 293 | tailscale_stoper 294 | 295 | if [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 296 | opkg remove tailscale 297 | fi 298 | 299 | # Remove tailscale or tailscaled files in specified directories 300 | local directories="/etc/init.d /etc /etc/config /usr/bin /usr/sbin /tmp /var/lib" 301 | local binaries="tailscale tailscaled" 302 | 303 | # Remove tailscale or tailscaled files in specified directories 304 | for dir in $directories; do 305 | for bin in $binaries; do 306 | if [ -f "$dir/$bin" ]; then 307 | rm -rf $dir/$bin 308 | echo "[INFO]: Deleted file: $dir/$bin" 309 | fi 310 | done 311 | done 312 | 313 | ip link delete tailscale0 314 | script_exit 315 | else 316 | echo "[INFO]: Cancel uninstall" 317 | break 318 | fi 319 | done 320 | } 321 | 322 | # Function: Clean Unknown Files 323 | remove_unknown_file() { 324 | while true; do 325 | echo "╔═══════════════════════════════════════════════════════╗" 326 | echo "║ [WARNING]!!! Please confirm the following: ║" 327 | echo "║ ║" 328 | echo "║ You are deleting Tailscale residual files. If these ║" 329 | echo "║ files were created by you, they should not be deleted.║" 330 | echo "║ Please cancel this operation! ║" 331 | echo "║ Please confirm your operation to avoid loss! ║" 332 | echo "║ ║" 333 | echo "╚═══════════════════════════════════════════════════════╝" 334 | 335 | # Remove tailscale or tailscaled files in specified directories 336 | local directories="/etc/init.d /etc /etc/config /usr/bin /usr/sbin /tmp /var/lib" 337 | local files="tailscale tailscaled" 338 | 339 | for dir in $directories; do 340 | for file in $files; do 341 | if [ -f "$dir/$file" ]; then 342 | echo "[INFO]: Found file: $dir/$file" 343 | fi 344 | done 345 | done 346 | 347 | read -n 1 -p "Confirm delete residual files? (y/N): " choice 348 | 349 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 350 | tailscale_stoper 351 | 352 | for dir in $directories; do 353 | for file in $files; do 354 | if [ -f "$dir/$file" ]; then 355 | rm -rf $dir/$file 356 | echo "[INFO]: Deleted file: $dir/$file" 357 | fi 358 | done 359 | done 360 | 361 | ip link delete tailscale0 362 | 363 | echo "[INFO]: All residual files deleted, restarting script..." 364 | sleep 2 365 | exec "$0" "$@" 366 | 367 | break 368 | else 369 | echo "[INFO]: Cancel delete residual files" 370 | break 371 | fi 372 | done 373 | } 374 | 375 | # Function: Clean Old Installation 376 | clean_old_installation() { 377 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 378 | echo "[INFO]: Cleaning old installation files..." 379 | local old_paths="/usr/bin/tailscale /usr/bin/tailscaled" 380 | for file in $old_paths; do 381 | if [ -f "$file" ]; then 382 | rm -f "$file" 383 | echo "[INFO]: Removed old file: $file" 384 | fi 385 | done 386 | fi 387 | } 388 | 389 | # Function: Persistent Installation 390 | persistent_install() { 391 | local confirm2persistent_install=$1 392 | local silent_install=$2 393 | 394 | if [ "$silent_install" != "true" ]; then 395 | echo "╔═══════════════════════════════════════════════════════╗" 396 | echo "║ [WARNING]!!! Please confirm the following: ║" 397 | echo "║ ║" 398 | echo "║ When using persistent installation, please ensure ║" 399 | echo "║ your OpenWrt has at least ${TAILSCALE_FILE_SIZE}M free space, ║" 400 | echo "║ recommended more than $(expr $TAILSCALE_FILE_SIZE \* 3)M. ║" 401 | echo "║ If any error occurs during installation, you can ║" 402 | echo "║ report at: $REPO_URL/issues ║" 403 | echo "║ Provide feedback. Thank you for using! /<3 ║" 404 | echo "║ ║" 405 | echo "╚═══════════════════════════════════════════════════════╝" 406 | read -n 1 -p "Confirm using persistent installation method to install tailscale? (y/N): " choice 407 | 408 | if [ "$choice" != "Y" ] && [ "$choice" != "y" ]; then 409 | return 410 | fi 411 | fi 412 | 413 | echo "" 414 | clean_old_installation 415 | 416 | if [ "$confirm2persistent_install" = "true" ]; then 417 | tailscale_stoper 418 | rm -rf /tmp/tailscale 419 | rm -rf /tmp/tailscaled 420 | rm -rf /usr/sbin/tailscale 421 | rm -rf /usr/sbin/tailscaled 422 | fi 423 | 424 | echo "" 425 | echo "[INFO]: Persistent installation in progress..." 426 | downloader 427 | opkg install /tmp/$TAILSCALE_FILE.ipk 428 | 429 | rm -rf "$TAILSCALE_FILE.ipk" "/tmp/$TAILSCALE_FILE.sha256" 430 | 431 | echo "" 432 | echo "╔═══════════════════════════════════════════════════════╗" 433 | echo "║ Tailscale installation & service startup complete!!! ║" 434 | echo "║ ║" 435 | echo "║ You can now start using it as you wish! ║" 436 | echo "║ Direct startup: tailscale up ║" 437 | echo "║ If any problems occur after installation, you can ║" 438 | echo "║ report at: $REPO_URL/issues ║" 439 | echo "║ Provide feedback. Thank you for using! /<3 ║" 440 | echo "║ ║" 441 | echo "╚═══════════════════════════════════════════════════════╝" 442 | echo "" 443 | 444 | echo "[INFO]: Re-initializing script, please wait..." 445 | init "" "false" 446 | } 447 | 448 | # Function: Switch from Temporary to Persistent Installation 449 | temp_to_persistent() { 450 | persistent_install "true" 451 | } 452 | 453 | # Function: Temporary Installation 454 | temp_install() { 455 | local confirm2temp_install=$1 456 | local silent_install=$2 457 | 458 | if [ "$silent_install" != "true" ]; then 459 | echo "╔═══════════════════════════════════════════════════════╗" 460 | echo "║ [WARNING]!!! Please confirm the following: ║" 461 | echo "║ ║" 462 | echo "║ Temporary installation places tailscale files in /tmp ║" 463 | echo "║ directory, /tmp directory will be cleared after ║" 464 | echo "║ device restart. If the script fails to re-download ║" 465 | echo "║ tailscale after restart, tailscale will not work ║" 466 | echo "║ properly, all your services relying on tailscale will ║" 467 | echo "║ fail. Please understand and confirm this information ║" 468 | echo "║ to avoid loss. Thank you! If persistent installation ║" 469 | echo "║ is possible, we recommend you use persistent method! ║" 470 | echo "║ If any error occurs during installation, you can ║" 471 | echo "║ report at: $REPO_URL/issues ║" 472 | echo "║ Provide feedback. Thank you for using! /<3 ║" 473 | echo "║ ║" 474 | echo "╚═══════════════════════════════════════════════════════╝" 475 | read -n 1 -p "Confirm using temporary installation method to install tailscale? (y/N): " choice 476 | 477 | if [ "$choice" != "Y" ] && [ "$choice" != "y" ]; then 478 | return 479 | fi 480 | fi 481 | 482 | echo "" 483 | clean_old_installation 484 | 485 | if [ "$confirm2temp_install" = "true" ]; then 486 | tailscale_stoper 487 | rm -rf /usr/sbin/tailscale 488 | rm -rf /usr/sbin/tailscaled 489 | fi 490 | 491 | echo "" 492 | echo "[INFO]: Temporary installation in progress..." 493 | downloader 494 | 495 | local ipk_file="/tmp/$TAILSCALE_FILE.ipk" 496 | local extract_dir="/tmp/ts_extract" 497 | 498 | mkdir -p "$extract_dir" 499 | 500 | echo "[INFO]: Extracting and deploying files..." 501 | tar -xOzf "$ipk_file" ./data.tar.gz 2>/dev/null | tar -xzC "$extract_dir" 2>/dev/null 502 | 503 | [ -d "$extract_dir/etc" ] && cp -r "$extract_dir/etc/"* /etc/ 504 | [ -d "$extract_dir/lib" ] && cp -r "$extract_dir/lib/"* /lib/ 505 | [ -f "$extract_dir/usr/sbin/tailscale" ] && mv "$extract_dir/usr/sbin/tailscale" /tmp/tailscale 506 | [ -f "$extract_dir/usr/sbin/tailscaled" ] && mv "$extract_dir/usr/sbin/tailscaled" /tmp/tailscaled 507 | 508 | echo "$TMP_TAILSCALE" > /usr/sbin/tailscale 509 | echo "$TMP_TAILSCALED" > /usr/sbin/tailscaled 510 | 511 | rm -rf "$extract_dir" "$ipk_file" "/tmp/$TAILSCALE_FILE.sha256" 512 | 513 | echo "[INFO]: Temporary installation complete!" 514 | echo "[INFO]: Starting tailscale service..." 515 | 516 | opkg update 517 | opkg install $PACKAGES_TO_CHECK 518 | 519 | chmod +x /etc/init.d/tailscale 520 | chmod +x /usr/sbin/tailscale 521 | chmod +x /usr/sbin/tailscaled 522 | chmod +x /tmp/tailscale 523 | chmod +x /tmp/tailscaled 524 | 525 | /etc/init.d/tailscale enable 526 | /etc/init.d/tailscale start 527 | 528 | sleep 3 529 | 530 | tailscaled &>/dev/null & 531 | if [ "$TMP_INSTALL" == "true" ]; then 532 | tailscale up 533 | fi 534 | echo "[INFO]: Tailscale service startup complete" 535 | echo "" 536 | echo "╔═══════════════════════════════════════════════════════╗" 537 | echo "║ Tailscale installation & service startup complete!!! ║" 538 | echo "║ ║" 539 | echo "║ You can now start using it as you wish! ║" 540 | echo "║ Direct startup: tailscale up ║" 541 | echo "║ If any problems occur after installation, you can ║" 542 | echo "║ report at: $REPO_URL/issues ║" 543 | echo "║ Provide feedback. Thank you for using! /<3 ║" 544 | echo "║ ║" 545 | echo "╚═══════════════════════════════════════════════════════╝" 546 | echo "" 547 | echo "[INFO]: Re-initializing script, please wait..." 548 | init "" "false" 549 | } 550 | 551 | # Function: Switch from Persistent to Temporary Installation 552 | persistent_to_temp() { 553 | temp_install "true" 554 | } 555 | 556 | # Function: Downloader 557 | downloader() { 558 | local attempt_range="1 2 3" 559 | local attempt_timeout=20 560 | 561 | local tmp="/tmp" 562 | local file_path="$tmp/$TAILSCALE_FILE.ipk" 563 | local tmp_packages="$tmp/Packages" 564 | local sha_file="$tmp/$TAILSCALE_FILE.sha256" 565 | local target_ipk="${TAILSCALE_FILE}.ipk" 566 | local download_url="${TAILSCALE_URL}/releases/latest/download" 567 | 568 | for attempt_times in $attempt_range; do 569 | if ! wget -cO "$file_path" "$download_url/$target_ipk"; then 570 | if [ "$attempt_times" == "3" ]; then 571 | echo "[ERROR]: Tailscale file failed to download three times, restarting script!" 572 | sleep 3 573 | init 574 | fi 575 | continue 576 | fi 577 | 578 | wget -q --timeout=$attempt_timeout "$download_url/Packages" -O "$tmp_packages" 579 | awk -v ipk="$target_ipk" -v path="$file_path" ' 580 | BEGIN { RS=""; FS="\n" } 581 | $0 ~ "Filename: " ipk { 582 | for(i=1; i<=NF; i++) { 583 | if($i ~ /^SHA256sum:/) { 584 | split($i, a, ": "); 585 | print a[2] " " path; 586 | exit; 587 | } 588 | } 589 | }' "$tmp_packages" > "$sha_file" 590 | 591 | if [ ! -s "$sha_file" ] || ! sha256sum -c "$sha_file" >/dev/null 2>&1; then 592 | if [ "$attempt_times" == "3" ]; then 593 | echo "[ERROR]: Tailscale file failed to download three times, restarting script, please retry!" 594 | rm -f "$file_path" "$sha_file" 595 | sleep 3 596 | init 597 | else 598 | echo "[INFO]: Tailscale file verification failed, trying to re-download!" 599 | rm -f "$file_path" "$sha_file" 600 | sleep 3 601 | fi 602 | else 603 | echo "[INFO]: Tailscale file verification passed!" 604 | rm -f "$sha_file" 605 | break 606 | fi 607 | done 608 | } 609 | 610 | # Function: Tailscale Service Stopper 611 | tailscale_stoper() { 612 | echo "" 613 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 614 | /etc/init.d/tailscale stop 615 | /tmp/tailscale down --accept-risk=lose-ssh 616 | /tmp/tailscale logout 617 | /etc/init.d/tailscale disable 618 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 619 | /etc/init.d/tailscale stop 620 | /usr/sbin/tailscale down --accept-risk=lose-ssh 621 | /usr/sbin/tailscale logout 622 | /etc/init.d/tailscale disable 623 | fi 624 | echo "" 625 | } 626 | 627 | # Function: Initialize 628 | init() { 629 | local show_init_progress_bar=$1 630 | 631 | local functions="check_device_target check_tailscale_install_status check_device_memory check_device_storage get_tailscale_info" 632 | local function_count=5 633 | local total=$function_count 634 | local progress=0 635 | 636 | if [ "$show_init_progress_bar" != "false" ]; then 637 | echo "" 638 | 639 | printf "\r[INFO] Initializing: [%-50s] %3d%%" "$(printf '='%.0s $(seq 1 "$progress"))" "$((progress * 2))" 640 | 641 | for function in $functions; do 642 | eval "$function" 643 | progress=$((progress + 1)) 644 | percent=$((progress * 100 / function_count)) 645 | bars=$((percent / 2)) 646 | printf "\r[INFO] Initializing: [%-50s] %3d%%" "$(printf '=%.0s' $(seq 1 "$bars"))" "$percent" 647 | done 648 | 649 | printf "\r[INFO] Complete : [%-50s] %3d%%" "$(printf '='%.0s $(seq 1 "$bars"))" "$percent" 650 | else 651 | for function in $functions; do 652 | eval "$function" 653 | done 654 | fi 655 | echo "" 656 | } 657 | 658 | # Function: Exit 659 | script_exit() { 660 | echo "┌───────────────────────────────────────────────────────┐" 661 | echo "│ THANKS!!! Thank you for your trust and use!!! │" 662 | echo "│ │" 663 | echo "│ If this script helps you, you can give a Star to │" 664 | echo "│ support me! │" 665 | echo "│ $REPO_URL/ │" 666 | echo "│ If any problems occur after installation, you can │" 667 | echo "│ report at: $REPO_URL/issues │" 668 | echo "│ Provide feedback. Thank you for using! /<3 │" 669 | echo "│ │" 670 | echo "└───────────────────────────────────────────────────────┘" 671 | exit 0 672 | } 673 | 674 | 675 | # Function: Show Basic Information 676 | show_info() { 677 | echo "╔═════════════════════ BASIC INFORMATION ═════════════════════╗" 678 | 679 | echo " Device Information:" 680 | echo " - Current Device TARGET: [${DEVICE_TARGET}]" 681 | echo " - Available / Total Storage Space: ($DEVICE_STORAGE_AVAILABLE / $DEVICE_STORAGE_TOTAL) M" 682 | echo " - Available / Total Memory: ($DEVICE_MEM_FREE / $DEVICE_MEM_TOTAL) M" 683 | echo " " 684 | 685 | echo " Local Tailscale Information:" 686 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 687 | echo " - Installation Status: Installed" 688 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 689 | echo " - Installation Mode: Temporary Installation" 690 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 691 | echo " - Installation Mode: Persistent Installation" 692 | fi 693 | echo " - Version: $TAILSCALE_LOCAL_VERSION" 694 | elif [ "$TAILSCALE_INSTALL_STATUS" = "unknown" ]; then 695 | echo " - Installation Status: Abnormal" 696 | echo " - Installation Mode: Unknown (tailscale file exists, but tailscale runs abnormally)" 697 | echo " - Version: Unknown" 698 | else 699 | echo " - Installation Status: Not Installed" 700 | echo " - Installation Mode: Not Installed" 701 | echo " - Version: Not Installed" 702 | 703 | fi 704 | 705 | echo " " 706 | echo " Latest Tailscale Information:" 707 | echo " - Version: $TAILSCALE_LATEST_VERSION" 708 | echo " - File Size: $TAILSCALE_FILE_SIZE M" 709 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 710 | if [ "$TAILSCALE_LATEST_VERSION" != "$TAILSCALE_LOCAL_VERSION" ]; then 711 | echo " - New version available, you can choose to update" 712 | else 713 | echo " - Already the latest version" 714 | fi 715 | fi 716 | 717 | echo " " 718 | echo " Tips:" 719 | if [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 720 | echo " - Persistent Installation: Available" 721 | else 722 | echo " - Persistent Installation: Not Available" 723 | fi 724 | if [ "$TAILSCALE_TEMP_INSTALLABLE" = "true" ]; then 725 | echo " - Temporary Installation: Available" 726 | else 727 | echo " - Temporary Installation: Not Available" 728 | fi 729 | if [ "$DEVICE_MEM_FREE" -lt 60 ]; then 730 | echo " - Device available memory too low, Tailscale may: Unable to run normally" 731 | elif [ "$DEVICE_MEM_FREE" -lt 120 ]; then 732 | echo " - Device available memory low, Tailscale may: Run sluggishly" 733 | fi 734 | 735 | echo "╚═════════════════════ BASIC INFORMATION ═════════════════════╝" 736 | } 737 | 738 | 739 | option_menu() { 740 | # Display menu and get user input 741 | while true; do 742 | local menu_items="" 743 | local menu_operations="" 744 | local option_index=1 745 | 746 | menu_items="$option_index).Show-Basic-Information" 747 | menu_operations="show_info" 748 | option_index=$((option_index + 1)) 749 | 750 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ] && [ "$TAILSCALE_LATEST_VERSION" != "$TAILSCALE_LOCAL_VERSION" ]; then 751 | menu_items="$menu_items $option_index).Update" 752 | menu_operations="$menu_operations update" 753 | option_index=$((option_index + 1)) 754 | fi 755 | 756 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 757 | menu_items="$menu_items $option_index).Uninstall" 758 | menu_operations="$menu_operations remove" 759 | option_index=$((option_index + 1)) 760 | fi 761 | 762 | if [ "$FOUND_TAILSCALE_FILE" = "true" ] && [ "$TAILSCALE_INSTALL_STATUS" = "unknown" ]; then 763 | menu_items="$menu_items $option_index).Delete-Residual-Files-(Found-tailscale-file-but-tailscale-runs-abnormally)" 764 | menu_operations="$menu_operations remove_unknown_file" 765 | option_index=$((option_index + 1)) 766 | fi 767 | 768 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ] && [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 769 | menu_items="$menu_items $option_index).Switch-to-Persistent-Installation" 770 | menu_operations="$menu_operations temp_to_persistent" 771 | option_index=$((option_index + 1)) 772 | fi 773 | 774 | if [ "$IS_TAILSCALE_INSTALLED" = "false" ] && [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 775 | menu_items="$menu_items $option_index).Persistent-Installation" 776 | menu_operations="$menu_operations persistent_install" 777 | option_index=$((option_index + 1)) 778 | fi 779 | 780 | if [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 781 | menu_items="$menu_items $option_index).Switch-to-Temporary-Installation" 782 | menu_operations="$menu_operations persistent_to_temp" 783 | option_index=$((option_index + 1)) 784 | fi 785 | 786 | if [ "$IS_TAILSCALE_INSTALLED" = "false" ]; then 787 | menu_items="$menu_items $option_index).Temporary-Installation" 788 | menu_operations="$menu_operations temp_install" 789 | option_index=$((option_index + 1)) 790 | fi 791 | 792 | menu_items="$menu_items $option_index).Exit" 793 | menu_operations="$menu_operations exit" 794 | 795 | echo "" 796 | echo "┌───────────────────────── MENU ─────────────────────────┐" 797 | 798 | # Traverse option list, dynamically generate menu 799 | for item in $menu_items; do 800 | echo "│ $item" 801 | done 802 | echo "" 803 | 804 | read -n 1 -p "│ Please enter option (1 ~ $option_index): " choice 805 | echo "" 806 | echo "" 807 | 808 | # Determine if input is legal 809 | if [ "$choice" -ge 1 ] && [ "$choice" -le "$option_index" ]; then 810 | operation_index=1 811 | for operation in $menu_operations; do 812 | if [ "$operation_index" = "$choice" ]; then 813 | eval "$operation" 814 | fi 815 | operation_index=$((operation_index + 1)) 816 | done 817 | echo "" 818 | else 819 | echo "[WARNING]: Invalid option, please try again!" 820 | echo "" 821 | break 822 | fi 823 | done 824 | } 825 | 826 | show_help() { 827 | echo "Tailscale on OpenWrt installer script. $SCRIPT_VERSION" 828 | echo "$REPO_URL" 829 | echo " Usage: " 830 | echo " --help: Show this help" 831 | echo " --tempinstall: Temporary installation mode" 832 | 833 | } 834 | 835 | 836 | # Read Parameters 837 | for arg in "$@"; do 838 | case $arg in 839 | --help) 840 | show_help 841 | exit 0 842 | ;; 843 | --tempinstall) 844 | TMP_INSTALL="true" 845 | ;; 846 | *) 847 | echo "[ERROR]: Unknown argument: $arg" 848 | show_help 849 | ;; 850 | esac 851 | done 852 | 853 | # Main Program 854 | 855 | main() { 856 | clear 857 | script_info 858 | init 859 | sleep 1 860 | clear 861 | script_info 862 | option_menu 863 | } 864 | 865 | if [ "$TMP_INSTALL" = "true" ]; then 866 | check_device_target 867 | get_tailscale_info 868 | temp_install "" "true" 869 | exit 0 870 | fi 871 | 872 | main -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 脚本信息 4 | SCRIPT_VERSION="v1.08" 5 | SCRIPT_DATE="2025/12/15" 6 | 7 | # 基本配置 8 | REPO_URL="https://github.com/GuNanOvO/openwrt-tailscale" 9 | REPO="gunanovo/openwrt-tailscale" 10 | TAILSCALE_URL="${REPO}/releases/latest" 11 | TAILSCALE_FILE="" # 由get_tailscale_info设置 12 | PACKAGES_TO_CHECK="libc kmod-tun ca-bundle" 13 | 14 | # 代理头 15 | URL_PROXYS="https://ghfast.top/https://github.com 16 | https://cf.ghproxy.cc/https://github.com 17 | https://www.ghproxy.cc/https://github.com 18 | https://gh-proxy.com/https://github.com 19 | https://ghproxy.cc/https://github.com 20 | https://ghproxy.cn/https://github.com 21 | https://www.ghproxy.cn/https://github.com 22 | https://github.com" 23 | # 使用自定义代理头 24 | USE_CUSTOM_PROXY="false" 25 | # 可用代理头 26 | AVAILABLE_PROXY="" # 由test_proxy设置 27 | 28 | # TMP安装 [/usr/sbin/tailscale] 29 | TMP_TAILSCALE='#!/bin/sh 30 | set -e 31 | 32 | if [ -f "/tmp/tailscale" ]; then 33 | /tmp/tailscale "$@" 34 | fi' 35 | # TMP安装 [/usr/sbin/tailscaled] 36 | TMP_TAILSCALED='#!/bin/sh 37 | set -e 38 | if [ -f "/tmp/tailscaled" ]; then 39 | /tmp/tailscaled "$@" 40 | else 41 | /usr/sbin/install.sh --tempinstall 42 | /tmp/tailscaled "$@" 43 | fi' 44 | 45 | TAILSCALE_LATEST_VERSION="" # 由get_tailscale_info设置 46 | TAILSCALE_LOCAL_VERSION="" 47 | IS_TAILSCALE_INSTALLED="false" 48 | TAILSCALE_INSTALL_STATUS="none" 49 | FOUND_TAILSCALE_FILE="false" 50 | 51 | DEVICE_TARGET="" 52 | DEVICE_MEM_TOTAL="" 53 | DEVICE_MEM_FREE="" 54 | DEVICE_STORAGE_TOTAL="" 55 | DEVICE_STORAGE_AVAILABLE="" 56 | TAILSCALE_FILE_SIZE="" # 由get_tailscale_info设置 57 | 58 | TAILSCALE_PERSISTENT_INSTALLABLE="" 59 | TAILSCALE_TEMP_INSTALLABLE="" 60 | 61 | ENABLE_INIT_PROGRESS_BAR="true" 62 | 63 | 64 | # 函数:脚本信息 65 | script_info() { 66 | echo "#╔╦╗┌─┐ ┬ ┬ ┌─┐┌─┐┌─┐┬ ┌─┐ ┌─┐┌┐┌ ╔═╗┌─┐┌─┐┌┐┌ ╦ ╦ ┬─┐┌┬┐ ╦ ┌┐┌┌─┐┌┬┐┌─┐┬ ┬ ┌─┐┬─┐#" 67 | echo "# ║ ├─┤ │ │ └─┐│ ├─┤│ ├┤ │ ││││ ║ ║├─┘├┤ │││ ║║║ ├┬┘ │ ║ │││└─┐ │ ├─┤│ │ ├┤ ├┬┘#" 68 | echo "# ╩ ┴ ┴ ┴ ┴─┘└─┘└─┘┴ ┴┴─┘└─┘ └─┘┘└┘ ╚═╝┴ └─┘┘└┘ ╚╩╝ ┴└─ ┴ ╩ ┘└┘└─┘ ┴ ┴ ┴┴─┘┴─┘└─┘┴└─#" 69 | echo "┌────────────────────────────────────────────────────────────────────────────────────────┐" 70 | echo "│ 一个用于在OpenWrt上安装Tailscale或更新Tailscale或...的一个脚本。 │" 71 | echo "│ 项目地址: "$REPO_URL" │" 72 | echo "│ 脚本版本: "$SCRIPT_VERSION" │" 73 | echo "│ 更新日期: "$SCRIPT_DATE" │" 74 | echo "│ 感谢您的使用, 如有帮助, 还请点颗star /<3 │" 75 | echo "└────────────────────────────────────────────────────────────────────────────────────────┘" 76 | } 77 | 78 | # 函数:设置DNS 79 | set_system_dns() { 80 | cat < /etc/resolv.conf 81 | search lan 82 | nameserver 223.5.5.5 83 | nameserver 119.29.29.29 84 | EOF 85 | } 86 | 87 | # 函数:获取设备架构 88 | check_device_target() { 89 | local exclude_target="powerpc_64_e5500|powerpc_464fp|powerpc_8548|armeb_xscale" 90 | local raw_target="" 91 | 92 | raw_target=$(opkg print-architecture 2>/dev/null | awk '{print $2}' | grep -vE "all|noarch" | head -n 1) 93 | 94 | if [ -z "$raw_target" ]; then 95 | raw_target=$(grep "DISTRIB_ARCH" /etc/openwrt_release 2>/dev/null | awk -F"'" '{print $2}') 96 | fi 97 | 98 | if [ -z "$raw_target" ]; then 99 | echo "[ERROR]: 无法获取设备架构,脚本退出。" 100 | exit 1 101 | fi 102 | 103 | if echo "$raw_target" | grep -iqE "$exclude_target"; then 104 | echo "[ERROR]: 当前架构 [$raw_target] 在排除名单中,脚本退出。" 105 | exit 1 106 | fi 107 | 108 | DEVICE_TARGET=$(echo "$raw_target" | tr -d '[:space:]') 109 | } 110 | 111 | # 函数:检测tailscale安装状态 112 | check_tailscale_install_status() { 113 | local bin_bin="/usr/bin/tailscaled" 114 | local bin_sbin="/usr/sbin/tailscaled" 115 | local bin_tmp="/tmp/tailscaled" 116 | 117 | local has_bin=false 118 | local has_sbin=false 119 | local has_tmp=false 120 | local bin_is_script=false 121 | 122 | [ -f "$bin_bin" ] && has_bin=true 123 | [ -f "$bin_sbin" ] && has_sbin=true 124 | [ -f "$bin_tmp" ] && has_tmp=true 125 | 126 | if $has_bin; then 127 | if head -n 1 "$bin_bin" 2>/dev/null | grep -q "^#!"; then 128 | bin_is_script=true 129 | fi 130 | fi 131 | 132 | if $has_sbin; then 133 | if head -n 1 "$bin_sbin" 2>/dev/null | grep -q "^#!"; then 134 | bin_is_script=true 135 | fi 136 | fi 137 | 138 | if command -v tailscale >/dev/null 2>&1; then 139 | local version_output 140 | version_output=$(tailscale version 2>/dev/null | head -n 1 | tr -d '[:space:]') 141 | [ -n "$version_output" ] && TAILSCALE_LOCAL_VERSION="$version_output" 142 | fi 143 | 144 | # 灵活状态判定 145 | if $has_tmp; then 146 | if $bin_is_script; then 147 | # 核心场景:二进制在 tmp,usr 下是引导脚本 148 | TAILSCALE_INSTALL_STATUS="temp" 149 | IS_TAILSCALE_INSTALLED="true" 150 | elif $has_bin || $has_sbin; then 151 | # 冲突场景:tmp 有,usr 也有真实的二进制 152 | TAILSCALE_INSTALL_STATUS="unknown" 153 | IS_TAILSCALE_INSTALLED="true" 154 | else 155 | # 纯临时场景:只有 tmp 有 156 | TAILSCALE_INSTALL_STATUS="temp" 157 | IS_TAILSCALE_INSTALLED="true" 158 | fi 159 | elif $has_bin || $has_sbin; then 160 | # 持久化场景:usr/sbin 下有文件 161 | TAILSCALE_INSTALL_STATUS="persistent" 162 | IS_TAILSCALE_INSTALLED="true" 163 | else 164 | IS_TAILSCALE_INSTALLED="false" 165 | fi 166 | 167 | [ "$IS_TAILSCALE_INSTALLED" = "true" ] && FOUND_TAILSCALE_FILE="true" 168 | } 169 | 170 | # 函数:检查设备运行内存 171 | check_device_memory() { 172 | local mem_info=$(free 2>/dev/null | grep "Mem:") 173 | local mem_total_kb=$(echo "$mem_info" | awk '{print $2}') 174 | local mem_available_kb=$(echo "$mem_info" | awk '{print $7}') 175 | 176 | [ -z "$mem_available_kb" ] && mem_available_kb=$(echo "$mem_info" | awk '{print $4}') 177 | 178 | if [ -z "$mem_total_kb" ] || ! echo "$mem_total_kb" | grep -q '^[0-9]\+$'; then 179 | echo "[ERROR]: 无法识别设备总内存数值" && exit 1 180 | fi 181 | 182 | if [ -z "$mem_available_kb" ] || ! echo "$mem_available_kb" | grep -q '^[0-9]\+$'; then 183 | echo "[ERROR]: 无法识别设备可用内存数值" && exit 1 184 | fi 185 | 186 | DEVICE_MEM_TOTAL=$((mem_total_kb / 1024)) 187 | DEVICE_MEM_FREE=$((mem_available_kb / 1024)) 188 | } 189 | 190 | # 函数:检查设备存储空间 191 | check_device_storage() { 192 | local mount_point="${1:-/}" 193 | 194 | local storage_info=$(df -Pk "$mount_point") 195 | local storage_used_kb=$(echo "$storage_info" | awk 'NR==2 {print $(NF-3)}') 196 | local storage_available_kb=$(echo "$storage_info" | awk 'NR==2 {print $(NF-2)}') 197 | 198 | if [ -z "$storage_used_kb" ] || ! echo "$storage_used_kb" | grep -q '^[0-9]\+$'; then 199 | echo "[ERROR]: 无法识别 $mount_point 的已用空间数值" && exit 1 200 | fi 201 | 202 | if ! echo "$storage_available_kb" | grep -q '^[0-9]\+$'; then 203 | echo "[ERROR]: 无法识别 $mount_point 的可用空间数值" && exit 1 204 | fi 205 | 206 | DEVICE_STORAGE_TOTAL=$(( (storage_used_kb + storage_available_kb) / 1024 )) 207 | DEVICE_STORAGE_AVAILABLE=$((storage_available_kb / 1024)) 208 | } 209 | 210 | # 函数:测试proxy 211 | test_proxy() { 212 | local attempt_range="1 2 3" 213 | # 超时时间(秒) 214 | local attempt_timeout=10 215 | local version 216 | 217 | for attempt_times in $attempt_range; do 218 | for attempt_proxy in $URL_PROXYS; do 219 | attempt_url="$attempt_proxy/$TAILSCALE_URL/download/version" 220 | version=$(wget -qO- --timeout=$attempt_timeout "$attempt_url" | tr -d ' \n\r') 221 | 222 | if [ -n "$version" ] && [[ "$version" =~ ^[0-9] ]]; then 223 | AVAILABLE_PROXY="$attempt_proxy" 224 | break 2 225 | fi 226 | done 227 | done 228 | 229 | if [ "$USE_CUSTOM_PROXY" == "true" ] && [ -z "$AVAILABLE_PROXY" ]; then 230 | echo "" 231 | echo "[ERROR]: 您的自定义代理不可用, 脚本退出..." 232 | exit 1 233 | fi 234 | 235 | if [ -z "$AVAILABLE_PROXY" ]; then 236 | echo "[ERROR]: 所有代理均不可用, 脚本退出..." 237 | echo "1. 确保网络连接正常" 238 | echo "2. 重试" 239 | echo "3. 报告开发者" 240 | exit 1 241 | fi 242 | } 243 | 244 | # 函数:获取tailscale信息 245 | get_tailscale_info() { 246 | local version 247 | local file 248 | local file_size 249 | local tmp_packages="/tmp/Packages" 250 | # 尝试3次 251 | local attempt_range="1 2 3" 252 | # 超时时间(秒) 253 | local attempt_timeout=10 254 | 255 | for attempt_times in $attempt_range; do 256 | version=$(wget -qO- --timeout=$attempt_timeout "$AVAILABLE_PROXY/$TAILSCALE_URL/download/version" | tr -d ' \n\r') 257 | file="tailscale_${version}_${DEVICE_TARGET}" 258 | 259 | wget -q --timeout=$attempt_timeout "$AVAILABLE_PROXY/$TAILSCALE_URL/download/Packages" -O "$tmp_packages" 260 | file_size=$(awk -v ipk="${file}.ipk" ' 261 | BEGIN { RS=""; FS="\n" } 262 | $0 ~ ipk { 263 | for(i=1;i<=NF;i++) if($i ~ /^Installed-Size:/) { 264 | print $i; exit 265 | } 266 | }' "$tmp_packages" | awk '{print $2}') 267 | 268 | if [ -n "$version" ] && [ -n "$file_size" ]; then 269 | break 270 | else 271 | sleep 1 272 | fi 273 | done 274 | 275 | if [ -z "$version" ] || [ -z "$file_size" ]; then 276 | echo "" 277 | echo "[ERROR]: 无法获取 tailscale 版本或文件大小" 278 | echo "1. 确保网络连接正常" 279 | echo "2. 重试" 280 | echo "3. 报告开发者" 281 | exit 1 282 | fi 283 | 284 | TAILSCALE_LATEST_VERSION="$version" 285 | TAILSCALE_FILE="$file" 286 | TAILSCALE_FILE_SIZE=$((file_size / 1024 / 1024)) 287 | 288 | if [ "$DEVICE_STORAGE_AVAILABLE" -gt "$TAILSCALE_FILE_SIZE" ]; then 289 | TAILSCALE_PERSISTENT_INSTALLABLE="true" 290 | else 291 | TAILSCALE_PERSISTENT_INSTALLABLE="false" 292 | fi 293 | 294 | if [ "$DEVICE_MEM_FREE" -gt "$TAILSCALE_FILE_SIZE" ]; then 295 | TAILSCALE_TEMP_INSTALLABLE="true" 296 | else 297 | TAILSCALE_TEMP_INSTALLABLE="false" 298 | fi 299 | } 300 | 301 | # 函数:更新 302 | update() { 303 | echo "[INFO]: 正在更新..." 304 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 305 | temp_install "" "true" 306 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 307 | persistent_install "" "true" 308 | fi 309 | while true; do 310 | echo "╔═══════════════════════════════════════════════════════╗" 311 | echo "║ [WARNING]!!!请您确认以下信息: ║" 312 | echo "║ ║" 313 | echo "║ 您正在执行更新Tailscale, Tailscale需要重启, 如果您当 ║" 314 | echo "║ 当前正在通过Tailscale连接至设备有可能断开与设备的连接 ║" 315 | echo "║ 请您确认您的操作, 避免造成失! 感谢您的使用! ║" 316 | echo "║ ║" 317 | echo "╚═══════════════════════════════════════════════════════╝" 318 | 319 | read -n 1 -p "确认重启tailscale吗? (y/N): " choice 320 | 321 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 322 | /etc/init.d/tailscale stop 323 | /etc/init.d/tailscale start 324 | break 325 | else 326 | echo "[INFO]: 取消重启tailscale, 您稍后可自行通过命令 /etc/init.d/tailscale stop && /etc/init.d/tailscale start 来重启tailscale服务" 327 | break 328 | fi 329 | done 330 | } 331 | 332 | # 函数:卸载 333 | remove() { 334 | while true; do 335 | echo "╔═══════════════════════════════════════════════════════╗" 336 | echo "║ [WARNING]!!!请您确认以下信息: ║" 337 | echo "║ ║" 338 | echo "║ 您正在执行卸载Tailscale, 卸载后,您所有依托于Tailscale ║" 339 | echo "║ 的服务都将失效, 如果您当前正在通过Tailscale连接至设备 ║" 340 | echo "║ 则有可能断开与设备的连接, 请您确认您的操作, 避免造成 ║" 341 | echo "║ 损失! 感谢您的使用! ║" 342 | echo "║ ║" 343 | echo "╚═══════════════════════════════════════════════════════╝" 344 | 345 | read -n 1 -p "确认卸载tailscale吗? (y/N): " choice 346 | 347 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 348 | tailscale_stoper 349 | 350 | if [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 351 | opkg remove tailscale 352 | fi 353 | 354 | # remove指定目录的 tailscale 或 tailscaled 文件 355 | local directories="/etc/init.d /etc /etc/config /usr/bin /usr/sbin /tmp /var/lib" 356 | local binaries="tailscale tailscaled" 357 | 358 | # remove指定目录的 tailscale 或 tailscaled 文件 359 | for dir in $directories; do 360 | for bin in $binaries; do 361 | if [ -f "$dir/$bin" ]; then 362 | rm -rf $dir/$bin 363 | echo "[INFO]: 已删除文件: $dir/$bin" 364 | fi 365 | done 366 | done 367 | 368 | ip link delete tailscale0 369 | script_exit 370 | else 371 | echo "[INFO]: 取消卸载" 372 | break 373 | fi 374 | done 375 | } 376 | 377 | # 函数:清理未知文件 378 | remove_unknown_file() { 379 | while true; do 380 | echo "╔═══════════════════════════════════════════════════════╗" 381 | echo "║ [WARNING]!!!请您确认以下信息: ║" 382 | echo "║ ║" 383 | echo "║ 您正在执行删除Tailscale残留文件,如果这些文件为您自行 ║" 384 | echo "║ 创建,则不应该被删除,请您取消该操作! ║" 385 | echo "║ 请您确认您的操作, 避免造成损失! ║" 386 | echo "║ ║" 387 | echo "╚═══════════════════════════════════════════════════════╝" 388 | 389 | # remove指定目录的 tailscale 或 tailscaled 文件 390 | local directories="/etc/init.d /etc /etc/config /usr/bin /usr/sbin /tmp /var/lib" 391 | local files="tailscale tailscaled" 392 | 393 | for dir in $directories; do 394 | for file in $files; do 395 | if [ -f "$dir/$file" ]; then 396 | echo "[INFO]: 找到文件: $dir/$file" 397 | fi 398 | done 399 | done 400 | 401 | read -n 1 -p "确认删除残留文件吗? (y/N): " choice 402 | 403 | if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then 404 | tailscale_stoper 405 | 406 | for dir in $directories; do 407 | for file in $files; do 408 | if [ -f "$dir/$file" ]; then 409 | rm -rf $dir/$file 410 | echo "[INFO]: 已删除文件: $dir/$file" 411 | fi 412 | done 413 | done 414 | 415 | ip link delete tailscale0 416 | 417 | echo "[INFO]: 已删除所有残留文件, 重启脚本..." 418 | sleep 2 419 | exec "$0" "$@" 420 | 421 | break 422 | else 423 | echo "[INFO]: 取消删除残留文件" 424 | break 425 | fi 426 | done 427 | } 428 | 429 | # 函数:清理旧的安装文件 430 | clean_old_installation() { 431 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 432 | echo "[INFO]: 清理旧的安装文件..." 433 | local old_paths="/usr/bin/tailscale /usr/bin/tailscaled" 434 | for file in $old_paths; do 435 | if [ -f "$file" ]; then 436 | rm -f "$file" 437 | echo "[INFO]: 已删除旧文件: $file" 438 | fi 439 | done 440 | fi 441 | } 442 | 443 | # 函数:持久安装 444 | persistent_install() { 445 | local confirm2persistent_install=$1 446 | local silent_install=$2 447 | 448 | if [ "$silent_install" != "true" ]; then 449 | echo "╔═══════════════════════════════════════════════════════╗" 450 | echo "║ [WARNING]!!!请您确认以下信息: ║" 451 | echo "║ ║" 452 | echo "║ 使用持久安装时, 请您确认您的openwrt的剩余空间至少大于 ║" 453 | echo "║ "$TAILSCALE_FILE_SIZE", 推荐大于$(expr $TAILSCALE_FILE_SIZE \* 3)M. ║" 454 | echo "║ 安装时产生任何错误, 您可以于: ║" 455 | echo "║ "$REPO_URL"/issues ║" 456 | echo "║ 提出反馈. 谢谢您的使用! /<3 ║" 457 | echo "║ ║" 458 | echo "╚═══════════════════════════════════════════════════════╝" 459 | read -n 1 -p "确认采用持久安装方式安装tailscale吗? (y/N): " choice 460 | 461 | if [ "$choice" != "Y" ] && [ "$choice" != "y" ]; then 462 | return 463 | fi 464 | fi 465 | 466 | echo "" 467 | clean_old_installation 468 | 469 | if [ "$confirm2persistent_install" = "true" ]; then 470 | tailscale_stoper 471 | rm -rf /tmp/tailscale 472 | rm -rf /tmp/tailscaled 473 | rm -rf /usr/sbin/tailscale 474 | rm -rf /usr/sbin/tailscaled 475 | fi 476 | 477 | echo "" 478 | echo "[INFO]: 正在持久安装..." 479 | downloader 480 | opkg install /tmp/$TAILSCALE_FILE.ipk 481 | 482 | rm -rf "$TAILSCALE_FILE.ipk" "/tmp/$TAILSCALE_FILE.sha256" 483 | 484 | echo "" 485 | echo "╔═══════════════════════════════════════════════════════╗" 486 | echo "║ Tailscale安装&服务启动完成!!! ║" 487 | echo "║ ║" 488 | echo "║ 现在您可以按照您希望的方式开始使用! ║" 489 | echo "║ 直接启动: tailscale up ║" 490 | echo "║ 安装后有任何无法使用的问题, 可以于: ║" 491 | echo "║ "$REPO_URL"/issues ║" 492 | echo "║ 提出反馈. 谢谢您的使用! /<3 ║" 493 | echo "║ ║" 494 | echo "╚═══════════════════════════════════════════════════════╝" 495 | echo "" 496 | 497 | echo "[INFO]: 正在重新初始化脚本, 请稍候..." 498 | init "" "false" 499 | } 500 | 501 | # 函数:临时安装切换到持久安装 502 | temp_to_persistent() { 503 | persistent_install "true" 504 | } 505 | 506 | # 函数:临时安装 507 | temp_install() { 508 | local confirm2temp_install=$1 509 | local silent_install=$2 510 | 511 | if [ "$silent_install" != "true" ]; then 512 | echo "╔═══════════════════════════════════════════════════════╗" 513 | echo "║ [WARNING]!!!请您确认以下信息: ║" 514 | echo "║ ║" 515 | echo "║ 临时安装是将tailscale文件置于/tmp目录, /tmp目录会在重 ║" 516 | echo "║ 启设备后清空. 如果该脚本在重启后重新下载tailscale失败 ║" 517 | echo "║ 则tailscale将无法正常使用, 您所有依托于tailscale的服 ║" 518 | echo "║ 务都将失效, 请您明悉并确定该讯息, 以免造成损失. 谢谢! ║" 519 | echo "║ 如果可以持久安装,推荐您采取持久安装方式! ║" 520 | echo "║ 安装时产生任何错误, 您可以于: ║" 521 | echo "║ "$REPO_URL"/issues ║" 522 | echo "║ 提出反馈. 谢谢您的使用! /<3 ║" 523 | echo "║ ║" 524 | echo "╚═══════════════════════════════════════════════════════╝" 525 | read -n 1 -p "确认采用临时安装方式安装tailscale吗? (y/N): " choice 526 | 527 | if [ "$choice" != "Y" ] && [ "$choice" != "y" ]; then 528 | return 529 | fi 530 | fi 531 | 532 | echo "" 533 | clean_old_installation 534 | 535 | if [ "$confirm2temp_install" = "true" ]; then 536 | tailscale_stoper 537 | rm -rf /usr/sbin/tailscale 538 | rm -rf /usr/sbin/tailscaled 539 | fi 540 | 541 | echo "" 542 | echo "[INFO]: 正在临时安装..." 543 | downloader 544 | 545 | local ipk_file="/tmp/$TAILSCALE_FILE.ipk" 546 | local extract_dir="/tmp/ts_extract" 547 | 548 | mkdir -p "$extract_dir" 549 | 550 | echo "[INFO]: 正在解压并部署文件..." 551 | tar -xOzf "$ipk_file" ./data.tar.gz 2>/dev/null | tar -xzC "$extract_dir" 2>/dev/null 552 | 553 | [ -d "$extract_dir/etc" ] && cp -r "$extract_dir/etc/"* /etc/ 554 | [ -d "$extract_dir/lib" ] && cp -r "$extract_dir/lib/"* /lib/ 555 | [ -f "$extract_dir/usr/sbin/tailscale" ] && mv "$extract_dir/usr/sbin/tailscale" /tmp/tailscale 556 | [ -f "$extract_dir/usr/sbin/tailscaled" ] && mv "$extract_dir/usr/sbin/tailscaled" /tmp/tailscaled 557 | 558 | echo "$TMP_TAILSCALE" > /usr/sbin/tailscale 559 | echo "$TMP_TAILSCALED" > /usr/sbin/tailscaled 560 | 561 | rm -rf "$extract_dir" "$ipk_file" "/tmp/$TAILSCALE_FILE.sha256" 562 | 563 | echo "[INFO]: 临时安装完成!" 564 | echo "[INFO]: 正在启动tailscale服务..." 565 | 566 | opkg update 567 | opkg install $PACKAGES_TO_CHECK 568 | 569 | chmod +x /etc/init.d/tailscale 570 | chmod +x /usr/sbin/tailscale 571 | chmod +x /usr/sbin/tailscaled 572 | chmod +x /tmp/tailscale 573 | chmod +x /tmp/tailscaled 574 | 575 | /etc/init.d/tailscale enable 576 | /etc/init.d/tailscale start 577 | 578 | sleep 3 579 | 580 | tailscaled &>/dev/null & 581 | if [ "$TMP_INSTALL" == "true" ]; then 582 | tailscale up 583 | fi 584 | echo "[INFO]: tailscale服务启动完成" 585 | echo "" 586 | echo "╔═══════════════════════════════════════════════════════╗" 587 | echo "║ Tailscale安装&服务启动完成!!! ║" 588 | echo "║ ║" 589 | echo "║ 现在您可以按照您希望的方式开始使用! ║" 590 | echo "║ 直接启动: tailscale up ║" 591 | echo "║ 安装后有任何无法使用的问题, 可以于: ║" 592 | echo "║ "$REPO_URL"/issues ║" 593 | echo "║ 提出反馈. 谢谢您的使用! /<3 ║" 594 | echo "║ ║" 595 | echo "╚═══════════════════════════════════════════════════════╝" 596 | echo "" 597 | echo "[INFO]: 正在重新初始化脚本, 请稍候..." 598 | init "" "false" 599 | } 600 | 601 | # 函数:持久安装切换到临时安装 602 | persistent_to_temp() { 603 | temp_install "true" 604 | } 605 | 606 | # 函数:下载器 607 | downloader() { 608 | local attempt_range="1 2 3" 609 | local attempt_timeout=20 610 | 611 | local tmp="/tmp" 612 | local file_path="$tmp/$TAILSCALE_FILE.ipk" 613 | local tmp_packages="$tmp/Packages" 614 | local sha_file="$tmp/$TAILSCALE_FILE.sha256" 615 | local target_ipk="${TAILSCALE_FILE}.ipk" 616 | local download_url="${AVAILABLE_PROXY}/${TAILSCALE_URL}/download" 617 | for attempt_times in $attempt_range; do 618 | if ! wget -cO "$file_path" "$download_url/$target_ipk"; then 619 | [ "$attempt_times" == "3" ] && { echo "[ERROR]: tailscale 文件三次下载均失败, 即将重启脚本!"; sleep 3; init; } 620 | continue 621 | fi 622 | 623 | wget -q --timeout="$attempt_timeout" "$download_url/Packages" -O "$tmp_packages" 624 | awk -v ipk="$target_ipk" -v path="$file_path" ' 625 | BEGIN { RS=""; FS="\n" } 626 | $0 ~ "Filename: " ipk { 627 | for (i = 1; i <= NF; i++) { 628 | if ($i ~ /^SHA256sum:/) { 629 | split($i, a, ": "); 630 | print a[2] " " path; 631 | exit; 632 | } 633 | } 634 | }' "$tmp_packages" > "$sha_file" 635 | 636 | 637 | if [ ! -s "$sha_file" ] || ! sha256sum -c "$sha_file" >/dev/null 2>&1; then 638 | if [ "$attempt_times" == "3" ]; then 639 | echo "[ERROR]: tailscale 文件三次下载均失败, 即将重启脚本, 请重试!" 640 | sleep 3 641 | rm -f "$file_path" "$sha_file" 642 | init 643 | else 644 | echo "[INFO]: tailscale 文件校验不通过, 正在尝试重新下载!" 645 | rm -f "$file_path" "$sha_file" 646 | sleep 3 647 | fi 648 | else 649 | echo "[INFO]: tailscale 文件校验通过!" 650 | rm -f "$sha_file" 651 | break 652 | fi 653 | done 654 | } 655 | 656 | # 函数:tailscale服务停止器 657 | tailscale_stoper() { 658 | echo "" 659 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 660 | /etc/init.d/tailscale stop 661 | /tmp/tailscale down --accept-risk=lose-ssh 662 | /tmp/tailscale logout 663 | /etc/init.d/tailscale disable 664 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 665 | /etc/init.d/tailscale stop 666 | /usr/sbin/tailscale down --accept-risk=lose-ssh 667 | /usr/sbin/tailscale logout 668 | /etc/init.d/tailscale disable 669 | fi 670 | echo "" 671 | } 672 | 673 | # 函数:初始化 674 | init() { 675 | local show_init_progress_bar=$1 676 | local change_dns=$2 677 | 678 | local functions="check_device_target check_tailscale_install_status check_device_memory check_device_storage test_proxy get_tailscale_info" 679 | local function_count=6 680 | local total=$function_count 681 | local progress=0 682 | 683 | if [ "$show_init_progress_bar" != "false" ]; then 684 | 685 | if [ "$change_dns" != "false" ]; then 686 | #询问是否更改DNS 687 | read -n 1 -p "[WARNING]: 是否将系统DNS更改为(223.5.5.5,119.29.29.29)以提高解析速度? (y/N): " dns_choice 688 | if [ "$dns_choice" = "Y" ] || [ "$dns_choice" = "y" ]; then 689 | echo "" 690 | set_system_dns 691 | echo "[INFO]: 系统DNS已更改" 692 | fi 693 | fi 694 | 695 | echo "" 696 | 697 | printf "\r[INFO]初始化中: [%-50s] %3d%%" "$(printf '='%.0s $(seq 1 "$progress"))" "$((progress * 2))" 698 | 699 | for function in $functions; do 700 | eval "$function" 701 | progress=$((progress + 1)) 702 | percent=$((progress * 100 / function_count)) 703 | bars=$((percent / 2)) 704 | printf "\r[INFO]初始化中: [%-50s] %3d%%" "$(printf '=%.0s' $(seq 1 "$bars"))" "$percent" 705 | done 706 | 707 | printf "\r[INFO] 完成 : [%-50s] %3d%%" "$(printf '='%.0s $(seq 1 "$bars"))" "$percent" 708 | else 709 | for function in $functions; do 710 | eval "$function" 711 | done 712 | fi 713 | echo "" 714 | } 715 | 716 | # 函数:退出 717 | script_exit() { 718 | echo "┌───────────────────────────────────────────────────────┐" 719 | echo "│ THANKS!!!感谢您的信任与使用!!! │" 720 | echo "│ │" 721 | echo "│ 如果该脚本对您有帮助, 您可以点一颗Star支持我! │" 722 | echo "│ "$REPO_URL"/ │" 723 | echo "│ 安装后产生无法使用等情况, 您可以于: │" 724 | echo "│ "$REPO_URL"/issues │" 725 | echo "│ 提出反馈. 谢谢您的使用! /<3 │" 726 | echo "│ │" 727 | echo "└───────────────────────────────────────────────────────┘" 728 | exit 0 729 | } 730 | 731 | 732 | # 函数:显示基本信息 733 | show_info() { 734 | echo "╔═════════════════════ 基 本 信 息 ═════════════════════╗" 735 | 736 | echo " 设备信息:" 737 | echo " - 当前设备TARGET:[${DEVICE_TARGET}]" 738 | echo " - 可用 / 所有 存储空间:($DEVICE_STORAGE_AVAILABLE / $DEVICE_STORAGE_TOTAL) M" 739 | echo " - 可用 / 所有 内存:($DEVICE_MEM_FREE / $DEVICE_MEM_TOTAL) M" 740 | echo " " 741 | 742 | echo " 本地Tailscale信息:" 743 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 744 | echo " - 安装状态: 已安装" 745 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ]; then 746 | echo " - 安装模式: 临时安装" 747 | elif [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 748 | echo " - 安装模式: 持久安装" 749 | fi 750 | echo " - 版本: $TAILSCALE_LOCAL_VERSION" 751 | elif [ "$IS_TAILSCALE_INSTALLED" = "unknown" ]; then 752 | echo " - 安装状态: 异常" 753 | echo " - 安装模式: 未知(存在tailscale文件, 但tailscale运行异常)" 754 | echo " - 版本: 未知" 755 | else 756 | echo " - 安装状态: 未安装" 757 | echo " - 安装模式: 未安装" 758 | echo " - 版本: 未安装" 759 | 760 | fi 761 | 762 | echo " " 763 | echo " 最新Tailscale信息:" 764 | echo " - 版本: $TAILSCALE_LATEST_VERSION" 765 | echo " - 文件大小: $TAILSCALE_FILE_SIZE M" 766 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 767 | if [ "$TAILSCALE_LATEST_VERSION" != "$TAILSCALE_LOCAL_VERSION" ]; then 768 | echo " - 有新版本可用, 您可以选择更新" 769 | else 770 | echo " - 已是最新版本" 771 | fi 772 | fi 773 | 774 | echo " " 775 | echo " 提示:" 776 | if [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 777 | echo " - 持久安装:可用" 778 | else 779 | echo " - 持久安装:不可用" 780 | fi 781 | if [ "$TAILSCALE_TEMP_INSTALLABLE" = "true" ]; then 782 | echo " - 临时安装:可用" 783 | else 784 | echo " - 临时安装:不可用" 785 | fi 786 | if [ "$DEVICE_MEM_FREE" -lt 60 ]; then 787 | echo " - 设备可用运行内存过低, Tailscale将:可能无法正常运行" 788 | elif [ "$DEVICE_MEM_FREE" -lt 120 ]; then 789 | echo " - 设备可用运行内存较低, Tailscale将:可能运行卡顿" 790 | fi 791 | 792 | echo " " 793 | echo " 代理:" 794 | if [ "$USE_CUSTOM_PROXY" = "true" ]; then 795 | echo " - GitHub代理: $AVAILABLE_PROXY (自定义)" 796 | else 797 | echo " - GitHub代理: $AVAILABLE_PROXY" 798 | fi 799 | 800 | echo "╚═════════════════════ 基 本 信 息 ═════════════════════╝" 801 | } 802 | 803 | 804 | option_menu() { 805 | # 显示菜单并获取用户输入 806 | while true; do 807 | local menu_items="" 808 | local menu_operations="" 809 | local option_index=1 810 | 811 | menu_items="$option_index).显示基本信息" 812 | menu_operations="show_info" 813 | option_index=$((option_index + 1)) 814 | 815 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ] && [ "$TAILSCALE_LATEST_VERSION" != "$TAILSCALE_LOCAL_VERSION" ]; then 816 | menu_items="$menu_items $option_index).更新" 817 | menu_operations="$menu_operations update" 818 | option_index=$((option_index + 1)) 819 | fi 820 | 821 | if [ "$IS_TAILSCALE_INSTALLED" = "true" ]; then 822 | menu_items="$menu_items $option_index).卸载" 823 | menu_operations="$menu_operations remove" 824 | option_index=$((option_index + 1)) 825 | fi 826 | 827 | if [ "$FOUND_TAILSCALE_FILE" = "true" ] && [ "$IS_TAILSCALE_INSTALLED" = "unknown" ]; then 828 | menu_items="$menu_items $option_index).删除残留文件(已找到tailscale文件但tailscale运行异常)" 829 | menu_operations="$menu_operations remove_unknown_file" 830 | option_index=$((option_index + 1)) 831 | fi 832 | 833 | if [ "$TAILSCALE_INSTALL_STATUS" = "temp" ] && [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 834 | menu_items="$menu_items $option_index).切换至持久安装" 835 | menu_operations="$menu_operations temp_to_persistent" 836 | option_index=$((option_index + 1)) 837 | fi 838 | 839 | if [ "$IS_TAILSCALE_INSTALLED" = "false" ] && [ "$TAILSCALE_PERSISTENT_INSTALLABLE" = "true" ]; then 840 | menu_items="$menu_items $option_index).持久安装" 841 | menu_operations="$menu_operations persistent_install" 842 | option_index=$((option_index + 1)) 843 | fi 844 | 845 | if [ "$TAILSCALE_INSTALL_STATUS" = "persistent" ]; then 846 | menu_items="$menu_items $option_index).切换至临时安装" 847 | menu_operations="$menu_operations persistent_to_temp" 848 | option_index=$((option_index + 1)) 849 | fi 850 | 851 | if [ "$IS_TAILSCALE_INSTALLED" = "false" ]; then 852 | menu_items="$menu_items $option_index).临时安装" 853 | menu_operations="$menu_operations temp_install" 854 | option_index=$((option_index + 1)) 855 | fi 856 | 857 | menu_items="$menu_items $option_index).退出" 858 | menu_operations="$menu_operations exit" 859 | 860 | echo "" 861 | echo "┌──────────────────────── 菜 单 ────────────────────────┐" 862 | 863 | # 遍历选项列表,动态生成菜单 864 | for item in $menu_items; do 865 | echo "│ $item" 866 | done 867 | echo "" 868 | 869 | read -n 1 -p "│ 请输入选项(0 ~ $option_index): " choice 870 | echo "" 871 | echo "" 872 | 873 | # 判断输入是否合法 874 | if [ "$choice" -ge 0 ] && [ "$choice" -le "$option_index" ]; then 875 | operation_index=1 876 | for operation in $menu_operations; do 877 | if [ "$operation_index" = "$choice" ]; then 878 | eval "$operation" 879 | fi 880 | operation_index=$((operation_index + 1)) 881 | done 882 | echo "" 883 | else 884 | echo "[WARNING]: 无效选项,请重试!" 885 | echo "" 886 | break 887 | fi 888 | done 889 | } 890 | 891 | show_help() { 892 | echo "Tailscale on OpenWrt installer script. $SCRIPT_VERSION" 893 | echo ""$REPO_URL"" 894 | echo " Usage: " 895 | echo " --help: Show this help" 896 | echo " --custom-proxy: Custom github proxy" 897 | 898 | } 899 | 900 | 901 | # 读取参数 902 | for arg in "$@"; do 903 | case $arg in 904 | --help) 905 | show_help 906 | exit 0 907 | ;; 908 | --tempinstall) 909 | TMP_INSTALL="true" 910 | ;; 911 | --custom-proxy) 912 | while true; do 913 | echo "╔═══════════════════════════════════════════════════════╗" 914 | echo "║ [WARNING]!!!请您确认以下信息: ║" 915 | echo "║ ║" 916 | echo "║ 您正在自定义GitHub代理, 请您确保您的代理有效, 否则脚 ║" 917 | echo "║ 本将无法正常运行, 确保格式如下: ║" 918 | echo "║ https://example.com ║" 919 | echo "║ ║" 920 | echo "║ 如果您有可用代理, 您可以提出issues, 我会将该代理加入 ║" 921 | echo "║ 脚本, 这将帮助大家, 谢谢!!! ║" 922 | echo "║ "$REPO_URL"/issues ║" 923 | echo "║ ║" 924 | echo "╚═══════════════════════════════════════════════════════╝" 925 | read -p "请输入您想要使用的代理并按回车: " custom_proxy 926 | while true; do 927 | echo "[INFO]: 您自定义的代理是: $custom_proxy" 928 | read -n 1 -p "您确定使用该代理吗? (y/N): " choise 929 | if [ "$choise" == "y" ] || [ "$choise" == "Y" ]; then 930 | USE_CUSTOM_PROXY="true" 931 | URL_PROXYS="$custom_proxy/https://github.com/" 932 | break 2 933 | else 934 | break 935 | fi 936 | done 937 | done 938 | ;; 939 | *) 940 | echo "[ERROR]: Unknown argument: $arg" 941 | show_help 942 | ;; 943 | esac 944 | done 945 | 946 | # 主程序 947 | 948 | main() { 949 | clear 950 | script_info 951 | init 952 | sleep 1 953 | clear 954 | script_info 955 | option_menu 956 | } 957 | 958 | if [ "$TMP_INSTALL" = "true" ]; then 959 | check_device_target 960 | get_tailscale_info 961 | temp_install "" "true" 962 | exit 0 963 | fi 964 | 965 | main --------------------------------------------------------------------------------