#!/usr/bin/env bash # data-wifi 用户目录安装器。不会调用 sudo,也不会修改 shell 配置。 set -euo pipefail main() { local source_url="${DATA_WIFI_INSTALL_URL:-https://datawifi.clinative.com}" local install_root="${XDG_DATA_HOME:-$HOME/.local/share}/data-wifi" local bin_dir="${DATA_WIFI_BIN_DIR:-$HOME/.local/bin}" local work_dir filename checksum release_dir command target case "$(uname -s)" in Darwin|Linux) ;; *) printf '请在 macOS、Linux 或 Windows WSL 中安装。\n' >&2; return 1 ;; esac for command in node curl tar; do command -v "$command" >/dev/null 2>&1 || { printf '缺少 %s;请先安装 Node.js 20+、curl 和 tar。\n' "$command" >&2; return 1; } done node -e 'if(Number(process.versions.node.split(".")[0])<20){console.error("需要 Node.js 20 或更新版本:https://nodejs.org/");process.exit(1)}' case "$source_url" in https://*) ;; http://127.0.0.1:*|http://localhost:*) ;; *) printf '安装来源必须使用 HTTPS。\n' >&2; return 1 ;; esac case "$install_root" in /*) ;; *) printf 'XDG_DATA_HOME 必须是绝对路径。\n' >&2; return 1 ;; esac case "$bin_dir" in /*) ;; *) printf 'DATA_WIFI_BIN_DIR 必须是绝对路径。\n' >&2; return 1 ;; esac for command in send keygen decrypt pack render; do target="$bin_dir/data-wifi-$command" if [ -e "$target" ] || [ -L "$target" ]; then if [ ! -L "$target" ] || [ "$(readlink "$target")" != "$install_root/current/bin/data-wifi-$command.cjs" ]; then printf '不会覆盖已有命令:%s\n' "$target" >&2; return 1 fi fi done if [ -e "$install_root/current" ] && [ ! -L "$install_root/current" ]; then printf '安装目录 current 已存在且不是链接,不会覆盖。\n' >&2; return 1 fi work_dir="$(mktemp -d "${TMPDIR:-/tmp}/data-wifi-install.XXXXXXXX")" trap "rm -rf -- $(printf '%q' "$work_dir")" EXIT printf '下载发送端…\n' curl --fail --silent --show-error --location --retry 2 "$source_url/releases/latest.json" -o "$work_dir/latest.json" filename="$(node -e 'let m=require(process.argv[1]);if(!/^data-wifi-[a-f0-9]{64}\.tar\.gz$/.test(m.filename))process.exit(1);console.log(m.filename)' "$work_dir/latest.json")" checksum="$(node -e 'let m=require(process.argv[1]);if(!/^[a-f0-9]{64}$/.test(m.sha256))process.exit(1);console.log(m.sha256)' "$work_dir/latest.json")" curl --fail --silent --show-error --location --retry 2 "$source_url/releases/$filename" -o "$work_dir/release.tar.gz" node -e 'let fs=require("node:fs"),c=require("node:crypto");let actual=c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex");if(actual!==process.argv[2]){console.error("安装包 SHA-256 校验失败");process.exit(1)}' "$work_dir/release.tar.gz" "$checksum" mkdir "$work_dir/unpacked" tar -xzf "$work_dir/release.tar.gz" -C "$work_dir/unpacked" for command in send keygen decrypt pack render; do [ -f "$work_dir/unpacked/bin/data-wifi-$command.cjs" ] || { printf '安装包不完整。\n' >&2; return 1; } chmod 755 "$work_dir/unpacked/bin/data-wifi-$command.cjs" done node "$work_dir/unpacked/bin/data-wifi-send.cjs" --help >/dev/null mkdir -p "$install_root/releases" "$bin_dir" release_dir="$install_root/releases/$checksum" if [ ! -d "$release_dir" ]; then mv "$work_dir/unpacked" "$release_dir"; fi # rename 原子切换链接;旧版本保留在 releases/,不会影响已有私钥或文件。 node -e 'let fs=require("node:fs"),p=require("node:path");let root=process.argv[1],release=process.argv[2],bin=process.argv[3];let next=p.join(root,".current-"+process.pid);fs.symlinkSync(release,next);fs.renameSync(next,p.join(root,"current"));for(let cmd of ["send","keygen","decrypt","pack","render"]){let target=p.join(bin,"data-wifi-"+cmd);if(!fs.existsSync(target))fs.symlinkSync(p.join(root,"current","bin","data-wifi-"+cmd+".cjs"),target)}' "$install_root" "$release_dir" "$bin_dir" printf '\n安装完成:%s\n' "$bin_dir" case ":$PATH:" in *":$bin_dir:"*) ;; *) printf '当前 shell 请执行:\n export PATH="%s:$PATH"\n也可将该行加入 ~/.zshrc 或 ~/.bashrc。\n' "$bin_dir" ;; esac printf '\n开始发送:data-wifi-send README.md\n手机接收:https://datawifi.clinative.com/dw/\n更新:重新运行同一安装命令。\n' rm -rf -- "$work_dir" trap - EXIT } main "$@"