#!/bin/bash ## bash only set -e # We strict the whole script CONFIG_BASE="/etc/natfrp" LOW_USER="natfrp" # Utility functions log_I() { echo -e "\e[32m[+] $1\e[0m"; } log_W() { echo -e "\e[33m[!] $1\e[0m"; } log_E() { echo -e "\e[31m[-] $1\e[0m"; } # Check root permission if [ "$EUID" -ne 0 ]; then log_E "请使用 root 用户运行此脚本" exit 1 fi # Check if selinux is enabled if command -v getenforce &>/dev/null; then if [[ $(getenforce) == "Enforcing" ]]; then log_W "SELinux 处于启用状态, 您可以继续运行脚本,但我们建议禁用 SELinux 以避免潜在问题" fi fi ask_for_creds() { while true; do read -e -p "请输入 SakuraFrp 的访问密钥 (可在 https://www.natfrp.com/user/ 获取): " api_key echo if [[ ${#api_key} -ge 16 ]]; then break; fi log_E "访问密钥错误, 请到 https://www.natfrp.com/user/ 获取" done while true; do while true; do read -e -p "请输入远程管理密码 (至少八个字符): " remote_pass echo if [[ ${#remote_pass} -ge 8 ]]; then break; fi log_E "远程管理密码至少需要 8 字符" done read -e -p "请再次输入远程管理密码: " remote_pass_confirm echo if [[ "$remote_pass" == "$remote_pass_confirm" ]]; then break; fi log_E "两次输入的远程管理密码不一致, 请确认知晓自己正在输入的内容" done clear } check_executable() { version=$(sudo -H -u "${LOW_USER}" "$1" -v) || { log_E "无法获取 $1 版本信息, 文件可能已损坏, 请尝试再次执行脚本重新下载" exit 1 } log_I "$1 版本: $version" } press_enter() { read -p "按 Enter 键以继续, 或 Ctrl-C 取消..." } # docker install should be the default docker_install() { echo -e "\e[96m[*] Docker 已安装, 将使用 Docker 安装模式\e[0m - 将在您的系统上创建名为 natfrp-service 的容器 - 将为您创建 ${CONFIG_BASE} 文件夹用于存储启动器配置文件\n" ask_for_creds mkdir -p ${CONFIG_BASE} || log_W "无法创建 ${CONFIG_BASE} 文件夹, 配置可能无法在容器重启后正常保存" if docker ps -a --format '{{.Names}}' | grep -q '^natfrp-service$'; then log_W "已存在名为 natfrp-service 的容器" read -p " - 是否移除已存在的容器? [y/N] " -r choice if [[ $choice =~ ^[Yy]$ ]]; then docker stop natfrp-service --timeout 5 &>/dev/null docker rm natfrp-service else log_E "请手动移除已存在的容器后重新运行脚本" exit 1 fi fi log_I "Docker 安装模式选择" echo "请选择镜像来源:" echo "1. 官方 (natfrp.com/launcher)" echo "2. GitHub (ghcr.io/natfrp/launcher)" echo "3. Docker Hub (natfrp/launcher)" read -p "请输入选项 [1-3] (默认1): " source case $source in 2) image="ghcr.io/natfrp/launcher" ;; 3) image="natfrp/launcher" ;; *) image="natfrp.com/launcher" ;; esac platform_flag="" if [[ $use_32bit -eq 1 ]]; then platform_flag="--platform linux/386" fi docker run -d \ --network=host \ --restart=always \ --pull=always \ --name=natfrp-service \ $platform_flag \ -v ${CONFIG_BASE}:/run:z \ -e "NATFRP_TOKEN=$api_key" \ -e "NATFRP_REMOTE=$remote_pass" \ -e "TZ=${TZ:-Asia/Shanghai}" \ $image || \ { log_E "Docker 模式安装失败, 请检查 Docker 在是否正常运行以及是否能正常访问镜像" exit 1 } echo -e "\n\e[96mDocker 模式安装成功, 您可使用下面的命令管理服务:\e[0m - 查看日志\tdocker logs natfrp-service - 停止服务\tdocker stop natfrp-service - 启动服务\tdocker start natfrp-service \n请登录远程管理界面启动隧道: https://www.natfrp.com/remote/v2\n" log_I "下方将输出启动器日志, 如需退出请按 Ctrl+C" docker logs -f natfrp-service exit 0 } # install all files, should be run before any other bareinstall_ functions bareinstall_base() { # Create user if ! id -u ${LOW_USER} &>/dev/null; then log_I "正在创建 ${LOW_USER} 用户" useradd -r -m -s /sbin/nologin ${LOW_USER} else echo -e "\e[32m[*] ${LOW_USER} 用户已存在\e[0m" fi # Download binaries cd "/home/${LOW_USER}" || { log_E "无法切换到 /home/${LOW_USER} 目录" exit 1 } do_download=1 if [[ -f frpc && -f natfrp-service ]]; then log_W "已存在 SakuraFrp 启动器文件" read -p " - 是否删除已有的文件重新下载? [y/N] " -r choice if [[ $choice =~ ^[Yy]$ ]]; then rm -f frpc natfrp-service else do_download=0 fi fi if [[ $do_download == 1 ]]; then log_I "正在下载启动器..." curl -Lo - "https://nya.globalslb.net/natfrp/client/launcher-unix/latest/natfrp-service_linux_$arch.tar.zst" | tar -xI zstd --overwrite chmod +x frpc natfrp-service chown ${LOW_USER}: frpc natfrp-service fi # Make sure binaries can be executed check_executable "./frpc" check_executable "./natfrp-service" # Update config file mkdir -p ${CONFIG_BASE} || log_W "无法创建 ${CONFIG_BASE} 文件夹, 配置文件夹可能已存在, 我们将尝试迁移" config_file="${CONFIG_BASE}/config.json" if [[ -f $config_file ]]; then log_W "已存在 SakuraFrp 启动器配置文件" read -p " - 是否清空设置? 这可能有助于修复问题: [y/N] " -r choice if [[ $choice =~ ^[Yy]$ ]]; then rm -f $config_file fi fi if [[ ! -f $config_file ]]; then echo '{}' > $config_file fi jq --arg token "$api_key" \ --arg remote_key "$(./natfrp-service remote-kdf "$remote_pass")" \ '. + { token: $token, remote_management: true, remote_management_key: $remote_key, log_stdout: true }' "$config_file" > "$config_file.tmp" mv $config_file.tmp $config_file chown -R ${LOW_USER}: ${CONFIG_BASE} } bareinstall_systemd() { bareinstall_base # Stop existing service to avoid conflicts systemctl stop natfrp.service &>/dev/null ||: # Install systemd unit log_I "正在安装 systemd 服务" cat >"/etc/systemd/system/natfrp.service" </home/${LOW_USER}/start.sh </dev/null docker rm natfrp-service &>/dev/null && log_I "已删除 Docker 容器" fi if [[ -f /etc/systemd/system/natfrp.service ]]; then if systemctl -q is-enabled natfrp.service; then systemctl disable --now natfrp.service &>/dev/null elif systemctl -q is-active natfrp.service; then systemctl stop natfrp.service &>/dev/null fi rm -f /etc/systemd/system/natfrp.service &>/dev/null && log_I "已删除 systemd 服务" systemctl daemon-reload &>/dev/null fi if [[ -f /home/${LOW_USER}/natfrp-service ]]; then rm -rf /home/${LOW_USER}/frpc /home/${LOW_USER}/natfrp-service &>/dev/null && log_I "已删除 /home/${LOW_USER} 中的程序文件" fi if id -u ${LOW_USER} &>/dev/null; then userdel ${LOW_USER} &>/dev/null && log_I "已删除 ${LOW_USER} 账户" fi read -p " - 是否删除启动器数据 (包括登录信息、日志等)? [y/N] " -r choice if [[ $choice =~ ^[Yy]$ ]]; then if [[ -d /home/${LOW_USER} ]]; then rm -rf /home/${LOW_USER} &>/dev/null && log_I "已删除 /home/${LOW_USER} 中的启动器数据" fi if [[ -d ${CONFIG_BASE} ]]; then rm -rf ${CONFIG_BASE} &>/dev/null && log_I "已删除 ${CONFIG_BASE} 中的启动器数据" fi else log_I "启动器数据未删除, 您可再次执行卸载脚本以删除" fi log_I "SakuraFrp 启动器已成功卸载" else log_I "SakuraFrp 启动器未被卸载" fi } ######### Main script start ######### # Detect ancient CPU on x86_64 systems use_32bit=0 if [[ $(uname -m) == "x86_64" ]]; then v2_flags=(popcnt sse4_2 ssse3) cpu_flags=$(grep -m1 '^flags' /proc/cpuinfo | cut -d: -f2) for f in "${v2_flags[@]}"; do if ! echo "$cpu_flags" | grep -qw "$f"; then use_32bit=1 break fi done if [[ $use_32bit -eq 1 ]]; then log_W "当前 CPU 不支持 x86-64-v2 指令集, 将使用 32 位 (x86) 版本" fi fi if [[ $1 == "uninstall" ]]; then uninstall exit 0 fi # Use docker if available if docker info &>/dev/null && [[ $1 != "direct" ]]; then docker_install exit 0 fi log_W "您正在使用非 Docker 安装模式" log_I "我们建议您总是使用 Docker 安装模式, 以便于管理和减少兼容性问题" # Check SELinux if command -v getenforce &>/dev/null; then if [[ $(getenforce) == "Enforcing" ]]; then log_E "SELinux 处于启用状态, 为避免出现问题, 请安装 Docker 后重新运行脚本以使用 Docker 部署" exit 1 fi fi # Install deps required_tools=(curl jq zstd sudo) missing_tools=() for tool in "${required_tools[@]}"; do if ! command -v "$tool" &>/dev/null; then missing_tools+=("$tool") fi done if [[ ${#missing_tools[@]} -ne 0 ]]; then log_I "正在使用包管理器安装必要的工具程序: ${missing_tools[*]}" if command -v apt-get &>/dev/null; then apt-get update && apt-get install -y "${missing_tools[@]}" elif command -v yum &>/dev/null; then yum install -y "${missing_tools[@]}" elif command -v zypper &>/dev/null; then zypper install -y "${missing_tools[@]}" elif command -v opkg &>/dev/null; then opkg update && opkg install "${missing_tools[@]}" elif command -v apk &>/dev/null; then apk --update add "${missing_tools[@]}" else log_E "当前系统的包管理器暂时不受支持, 请手动安装 ${missing_tools[*]} 后重新运行脚本" exit 1 fi fi # Migrate config files from old script if [[ -f "/home/natfrp/.config/natfrp-service/config.json" && ${CONFIG_BASE} != "/home/natfrp/.config/natfrp-service" ]]; then log_W "检测到旧的配置文件, 将为您迁移到 ${CONFIG_BASE} 目录" read -p " - 是否迁移配置文件? [y/N] " -r choice if [[ $choice =~ ^[Yy]$ ]]; then systemctl stop natfrp.service &>/dev/null || log_W "无法停止旧服务, 将尝试直接迁移" mkdir -p ${CONFIG_BASE} || log_W "无法创建 ${CONFIG_BASE} 文件夹, 配置文件夹可能已存在, 我们将尝试迁移" mv -f /home/natfrp/.config/natfrp-service/ ${CONFIG_BASE}/ || log_W "无法完整迁移旧配置文件, 数据迁移可能不完整" chown -R ${LOW_USER}: ${CONFIG_BASE} log_I "已迁移旧配置文件到 ${CONFIG_BASE} 目录" fi fi # Determine filename machine="$(uname -m)" case "$machine" in "x86_64") if [[ $use_32bit -eq 1 ]]; then arch="386" else arch="amd64" fi ;; "i386") arch="386" ;; "aarch64") arch="arm64" ;; "armv7l") arch="armv7" ;; "mips") arch="mips" ;; "mipsel") arch="mipsle" ;; "mips64") arch="mips64" ;; "mips64le") arch="mips64le" ;; "riscv64") arch="riscv64" ;; "loongarch64") arch="loong64" ;; *) log_E "不支持当前系统架构: $machine" exit 1 ;; esac ask_for_creds # Check init ls -l /proc/1/exe | grep -q systemd && is_systemd=1 || is_systemd=0 if [ $is_systemd -eq 1 ]; then echo -e "\e[96m[*] 将使用 systemd 安装模式\e[0m - 将在您的系统上创建名为 ${LOW_USER} 的用户 - 将在 /home/${LOW_USER} 目录下保存 SakuraFrp 启动器文件 - 将为您创建 ${CONFIG_BASE} 文件夹用于存储启动器配置文件 - 将创建 systemd 服务并启动 SakuraFrp 启动器\n" press_enter bareinstall_systemd else echo -e "\e[96m[*] 未发现 systemd, 将只安装启动器文件\e[0m - 将在您的系统上创建名为 ${LOW_USER} 的用户 - 将在 /home/${LOW_USER} 目录下保存 SakuraFrp 启动器文件 - 将为您创建 ${CONFIG_BASE} 文件夹用于存储启动器配置文件 - 将在 /home/${LOW_USER}/start.sh 创建一个脚本用于运行 SakuraFrp 启动器 \e[31m- 需手动启动, 不会开机自启 - 关闭后不会自动重启\e[0m\n" press_enter bareinstall_other fi