From 7ad623b5f294c73dea22cade3149d82f067122cb Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 22 Oct 2025 00:15:56 +0800 Subject: [PATCH 01/14] Update deploy_linux.sh --- Ritual/deploy_linux.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Ritual/deploy_linux.sh b/Ritual/deploy_linux.sh index 5fe47a7..9d39530 100644 --- a/Ritual/deploy_linux.sh +++ b/Ritual/deploy_linux.sh @@ -643,7 +643,8 @@ while [ $attempt -le $max_attempts ]; do fi ((attempt++)) done -warn "请确保私钥有足够余额以支付 gas 费用。" + +# 直接部署合约,无需 Gas 费用检查 deploy_log=$(mktemp) attempt=1 while [ $attempt -le $max_attempts ]; do @@ -803,4 +804,4 @@ monitor_and_skip_trie_error() { # 启动守护进程(后台运行) monitor_and_skip_trie_error & -exit 0 \ No newline at end of file +exit 0 From f0760473c2bcaa8c3d9dcbd18b8a856bb4a3177d Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 22 Oct 2025 00:46:09 +0800 Subject: [PATCH 02/14] Update deploy_linux.sh --- Ritual/deploy_linux.sh | 230 +++++++++++++---------------------------- 1 file changed, 69 insertions(+), 161 deletions(-) diff --git a/Ritual/deploy_linux.sh b/Ritual/deploy_linux.sh index 9d39530..f963e21 100644 --- a/Ritual/deploy_linux.sh +++ b/Ritual/deploy_linux.sh @@ -172,9 +172,10 @@ select yn in "是 (全新部署,清除并重装)" "否 (继续现有环境)" " esac done -# 检查端口是否占用 -info "检查端口占用..." +# 检查端口是否占用前先尝试停止并清理 Docker Compose 服务 for port in 4000 6379 8545 5001; do + echo "🧹 停止并清理当前 Docker Compose 服务..." + docker compose down || { echo "⚠️ docker compose down 执行失败,继续执行下一步..."; } if lsof -i :$port &> /dev/null; then info "端口 $port 被占用,尝试自动kill占用进程..." pids=$(lsof -t -i :$port) @@ -198,21 +199,15 @@ if [ "$skip_to_deploy" = "true" ] || ([ "$yn" != "退出" ] && [ "$update_config # 检查 RPC URL 连通性 echo "[9/15] 🔍 测试 RPC URL 连通性..." | tee -a "$log_file" - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do + while true; do chain_id=$(curl -s -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_chainId","id":1}' "$RPC_URL" | jq -r '.result') if [ -n "$chain_id" ]; then info "检测到链 ID: $chain_id" break else - warn "无法连接到 RPC URL 或无效响应,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "无法连接到 RPC URL,已达到最大重试次数 ($max_attempts)。请检查 RPC URL 或网络连接。" - fi + warn "无法连接到 RPC URL 或无效响应,正在重试..." sleep 10 fi - ((attempt++)) done fi @@ -249,41 +244,26 @@ if [ "$update_config_and_restart" = "true" ]; then # 检查并更新 docker-compose.yml 中的 depends_on 设置 info "检查并更新 docker-compose.yaml 中的 depends_on 设置..." if grep -q 'depends_on: \[ redis, infernet-anvil \]' docker-compose.yaml; then - sed -i 's/depends_on: \[ redis, infernet-anvil \]/depends_on: [ redis ]/g' docker-compose.yaml + sed -i.bak 's/depends_on: \[ redis, infernet-anvil \]/depends_on: [ redis ]/' docker-compose.yaml info "已修改 depends_on 配置。备份文件保存在:docker-compose.yaml.bak" else info "depends_on 配置已正确,无需修改。" fi - # 停止容器 - info "正在停止现有容器..." - if docker-compose down; then - info "容器已停止" - else - warn "停止容器时出现警告,继续执行..." + echo "🧹 停止并清理当前 Docker Compose 服务..." + docker compose down || { echo "⚠️ docker compose down 执行失败,继续执行下一步..."; } + + # 清理旧的 infernet-node 日志后台进程 + if pgrep -f "docker logs -f infernet-node" > /dev/null; then + pkill -f "docker logs -f infernet-node" + info "已清理旧的 infernet-node 日志后台进程" fi - - # 启动指定服务:node、redis、fluentbit - info "正在启动指定服务:node、redis、fluentbit..." - attempt=1 - max_attempts=5 - while [ $attempt -le $max_attempts ]; do - info "尝试启动容器 (第 $attempt 次)..." - if docker-compose up node redis fluentbit; then - info "容器启动成功" - # 启动日志后台保存 - (docker logs -f infernet-node > "$HOME/infernet-deployment.log" 2>&1 &) - break - else - warn "启动容器失败,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "启动容器失败,已达到最大重试次数 ($max_attempts)。请检查 Docker 配置或日志。" - fi - sleep 10 - fi - ((attempt++)) - done - + + echo "⚙️ 启动指定服务:node、redis、fluentbit" + docker compose up node redis fluentbit & + # 启动日志后台保存 + (docker logs -f infernet-node > "$HOME/infernet-deployment.log" 2>&1 &) + # 容器将在前台运行,脚本到此结束 echo "[8/8] ✅ 配置更新完成!容器已在前台启动。" | tee -a "$log_file" info "容器正在前台运行,按 Ctrl+C 可停止容器" @@ -296,9 +276,7 @@ if [ "$skip_to_deploy" = "true" ]; then # 检查 Foundry if ! command -v forge &> /dev/null; then info "Foundry 未安装,正在安装..." - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do + while true; do if curl -L https://foundry.paradigm.xyz | bash; then echo 'export PATH="$HOME/.foundry/bin:$PATH"' >> ~/.bashrc source ~/.bashrc @@ -306,16 +284,13 @@ if [ "$skip_to_deploy" = "true" ]; then info "Foundry 安装成功,forge 版本:$(forge --version)" break else - warn "Foundry 更新失败,第 $attempt/$max_attempts 次重试..." + warn "Foundry 更新失败,正在重试..." + sleep 10 fi else - warn "Foundry 安装失败,第 $attempt/$max_attempts 次重试..." - fi - if [ $attempt -eq $max_attempts ]; then - error "Foundry 安装失败,已达到最大重试次数 ($max_attempts)。请检查网络或权限。" + warn "Foundry 安装失败,正在重试..." + sleep 10 fi - sleep 10 - ((attempt++)) done else info "Foundry 已安装,forge 版本:$(forge --version)" @@ -330,66 +305,37 @@ if [ "$full_deploy" = "true" ] || [ ! -d "$HOME/infernet-container-starter" ]; t info "目录 $HOME/infernet-container-starter 已存在,正在删除..." rm -rf "$HOME/infernet-container-starter" || error "删除 $HOME/infernet-container-starter 失败,请检查权限。" fi - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do - info "尝试克隆仓库 (第 $attempt 次)..." - if timeout 300 git clone https://github.com/ritual-net/infernet-container-starter "$HOME/infernet-container-starter" 2> git_clone_error.log; then - if [ -d "$HOME/infernet-container-starter/deploy" ] && [ -d "$HOME/infernet-container-starter/projects/hello-world" ]; then - info "仓库克隆成功,内容验证通过。" - break - else - error "克隆的仓库内容不完整,缺少 deploy 或 projects/hello-world 目录。" - fi + while true; do + if git clone https://github.com/ritual-net/infernet-container-starter "$HOME/infernet-container-starter"; then + info "仓库克隆成功。" + break else - warn "克隆仓库失败,错误信息:$(cat git_clone_error.log)" - if [ $attempt -eq $max_attempts ]; then - error "克隆仓库失败,已达到最大重试次数 ($max_attempts)。请检查网络或 GitHub 访问权限。" - fi - warn "正在重试 ($attempt/$max_attempts)..." + warn "克隆仓库失败,正在重试..." sleep 10 fi - ((attempt++)) done else info "使用现有目录 $HOME/infernet-container-starter 继续部署..." - if [ ! -d "$HOME/infernet-container-starter/deploy" ] || \ - [ ! -d "$HOME/infernet-container-starter/projects/hello-world/contracts" ] || \ - [ ! -f "$HOME/infernet-container-starter/projects/hello-world/contracts/Makefile" ] || \ - [ ! -f "$HOME/infernet-container-starter/projects/hello-world/contracts/script/Deploy.s.sol" ]; then - error "现有目录 $HOME/infernet-container-starter 不完整,缺少必要文件或目录,请选择全新部署。" - fi fi -cd "$(realpath -m "$HOME/infernet-container-starter")" || error "无法进入 $HOME/infernet-container-starter 目录,请检查目录是否存在或是否为有效符号链接。" -info "当前工作目录:$(pwd)" -ls -la . || warn "目录 $HOME/infernet-container-starter 为空或无法访问。" +cd "$HOME/infernet-container-starter" || error "无法进入 $HOME/infernet-container-starter 目录。" echo "[11/15] 📦 拉取 hello-world 容器..." | tee -a "$log_file" -max_attempts=5 -attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do if curl -s --connect-timeout 5 https://registry-1.docker.io/ > /dev/null; then break else - warn "无法连接到 Docker Hub,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "无法连接到 Docker Hub,已达到最大重试次数 ($max_attempts)。请检查网络连接。" - fi + warn "无法连接到 Docker Hub,正在重试..." sleep 10 fi - ((attempt++)) done attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do info "尝试拉取 ritualnetwork/hello-world-infernet:latest (第 $attempt 次)..." if docker pull ritualnetwork/hello-world-infernet:latest; then info "镜像拉取成功。" break else - warn "拉取 hello-world 容器失败,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "拉取容器失败,已达到最大重试次数 ($max_attempts)。请检查 Docker 配置或网络。" - fi + warn "拉取 hello-world 容器失败,正在重试..." sleep 10 fi ((attempt++)) @@ -501,18 +447,15 @@ EOF if [ "$full_deploy" = "true" ]; then echo "[14/15] 🐳 启动 Docker 容器..." | tee -a "$log_file" attempt=1 - max_attempts=5 - while [ $attempt -le $max_attempts ]; do + while true; do info "尝试启动 Docker 容器 (第 $attempt 次)..." if docker-compose -f "$HOME/infernet-container-starter/deploy/docker-compose.yaml" up -d; then info "Docker 容器启动成功。" + # 启动日志后台保存 (docker logs -f infernet-node > "$HOME/infernet-deployment.log" 2>&1 &) break else - warn "启动 Docker 容器失败,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "启动容器失败,已达到最大重试次数 ($max_attempts)。请检查 Docker 配置或日志。" - fi + warn "启动 Docker 容器失败,正在重试..." sleep 10 fi ((attempt++)) @@ -522,77 +465,48 @@ fi echo "[15/15] 🛠️ 安装 Foundry..." | tee -a "$log_file" if ! command -v forge &> /dev/null; then info "Foundry 未安装,正在安装..." - max_attempts=5 - attempt=1 - while [ $attempt -le $max_attempts ]; do + while true; do if curl -L https://foundry.paradigm.xyz | bash; then - # 确保环境变量正确设置 - export PATH="$HOME/.foundry/bin:$PATH" - # 重新加载 bashrc - source ~/.bashrc 2>/dev/null || true - # 等待一下让安装完成 - sleep 5 - # 尝试运行 foundryup - if "$HOME/.foundry/bin/foundryup" 2>/dev/null || foundryup 2>/dev/null; then - # 再次确保环境变量加载 - export PATH="$HOME/.foundry/bin:$PATH" - source ~/.bashrc 2>/dev/null || true - # 检查 forge 是否可用 - if forge --version &>/dev/null; then - info "Foundry 安装成功,forge 版本:$(forge --version)" - break - else - warn "Foundry 安装完成但 forge 命令不可用,第 $attempt/$max_attempts 次重试..." - fi + echo 'export PATH="$HOME/.foundry/bin:$PATH"' >> ~/.bashrc + source ~/.bashrc + if foundryup; then + info "Foundry 安装成功,forge 版本:$(forge --version)" + break else - warn "Foundry 更新失败,第 $attempt/$max_attempts 次重试..." + warn "Foundry 更新失败,正在重试..." + sleep 10 fi else - warn "Foundry 安装失败,第 $attempt/$max_attempts 次重试..." - fi - if [ $attempt -eq $max_attempts ]; then - error "Foundry 安装失败,已达到最大重试次数 ($max_attempts)。请检查网络或权限。" + warn "Foundry 安装失败,正在重试..." + sleep 10 fi - sleep 10 - ((attempt++)) done else info "Foundry 已安装,forge 版本:$(forge --version)" fi echo "[16/16] 📚 安装 Forge 库..." | tee -a "$log_file" -cd "$HOME/infernet-container-starter/projects/hello-world/contracts" || error "无法进入 $HOME/infernet-container-starter/projects/hello-world/contracts 目录" +cd "$HOME/infernet-container-starter/projects/hello-world/contracts" if ! rm -rf lib/forge-std lib/infernet-sdk; then warn "清理旧 Forge 库失败,继续安装..." fi -max_attempts=5 -attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do if forge install foundry-rs/forge-std; then info "forge-std 安装成功。" break else - warn "安装 forge-std 失败,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "安装 forge-std 失败,已达到最大重试次数 ($max_attempts)。" - fi + warn "安装 forge-std 失败,正在重试..." sleep 10 fi - ((attempt++)) done -attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do if forge install ritual-net/infernet-sdk; then info "infernet-sdk 安装成功。" break else - warn "安装 infernet-sdk 失败,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "安装 infernet-sdk 失败,已达到最大重试次数 ($max_attempts)。" - fi + warn "安装 infernet-sdk 失败,正在重试..." sleep 10 fi - ((attempt++)) done echo "[17/17] 🔧 写入部署脚本..." | tee -a "$log_file" @@ -627,18 +541,14 @@ EOF echo "[19/19] 🚀 开始部署合约..." | tee -a "$log_file" cd "$HOME/infernet-container-starter/projects/hello-world/contracts" || error "无法进入 $HOME/infernet-container-starter/projects/hello-world/contracts 目录" -max_attempts=5 attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do info "尝试检查 RPC URL 连通性 (第 $attempt 次)..." if curl -s -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_chainId","id":1}' "$RPC_URL" | jq -e '.result' > /dev/null; then info "RPC URL 连通性检查成功。" break else - warn "RPC URL 无法连接,第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "RPC URL 无法连接,已达到最大重试次数 ($max_attempts)。请检查 RPC URL 或网络。" - fi + warn "RPC URL 无法连接,正在重试..." sleep 10 fi ((attempt++)) @@ -647,17 +557,14 @@ done # 直接部署合约,无需 Gas 费用检查 deploy_log=$(mktemp) attempt=1 -while [ $attempt -le $max_attempts ]; do +while true; do info "尝试部署合约 (第 $attempt 次)..." if PRIVATE_KEY="$PRIVATE_KEY" forge script script/Deploy.s.sol:Deploy --broadcast --rpc-url "$RPC_URL" > "$deploy_log" 2>&1; then info "🔺 合约部署成功!✅ 输出如下:" cat "$deploy_log" break else - warn "合约部署失败,详细信息如下:\n$(cat "$deploy_log")\n第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "合约部署失败,已达到最大重试次数 ($max_attempts)。请检查日志或网络。" - fi + warn "合约部署失败,详细信息如下:\n$(cat "$deploy_log")\n正在重试..." sleep 10 fi ((attempt++)) @@ -743,17 +650,14 @@ EOF info "正在调用合约..." call_log=$(mktemp) attempt=1 - while [ $attempt -le $max_attempts ]; do + while true; do info "尝试调用合约 (第 $attempt 次)..." if PRIVATE_KEY="$PRIVATE_KEY" forge script "$call_contract_file" --broadcast --rpc-url "$RPC_URL" > "$call_log" 2>&1; then info "✅ 合约调用成功!输出如下:" cat "$call_log" break else - warn "合约调用失败,详细信息如下:\n$(cat "$call_log")\n第 $attempt/$max_attempts 次重试..." - if [ $attempt -eq $max_attempts ]; then - error "合约调用失败,已达到最大重试次数 ($max_attempts)。请检查日志或网络。" - fi + warn "合约调用失败,详细信息如下:\n$(cat "$call_log")\n正在重试..." sleep 10 fi ((attempt++)) @@ -766,32 +670,36 @@ rm -f "$deploy_log" echo "[20/20] ✅ 部署完成!容器已在前台启动。" | tee -a "$log_file" info "容器正在前台运行,按 Ctrl+C 可停止容器" -info "请检查日志:docker logs infernet-node" -info "下一步:可运行 'forge script script/CallContract.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY' 来再次调用合约。" +info "容器启动后,脚本将自动退出" -# 自动跳过 missing trie node 区块并重启节点 +# ========== 自动跳过missing trie node区块并重启节点 =========== monitor_and_skip_trie_error() { LOG_FILE="$HOME/infernet-deployment.log" CONFIG_FILE="$HOME/infernet-container-starter/deploy/config.json" COMPOSE_DIR="$HOME/infernet-container-starter/deploy" LAST_BATCH_FILE="/tmp/ritual_last_batch.txt" - info "启动 missing trie node 自动跳过守护进程..." + info "启动missing trie node自动跳过守护进程..." while true; do + # 检查日志中是否有新的 missing trie node 错误 line=$(grep "missing trie node" "$LOG_FILE" | tail -1) if [[ -n "$line" ]]; then + # 提取 batch 区间 batch=$(echo "$line" | grep -oE "batch=\\([0-9]+, [0-9]+\\)") if [[ $batch =~ ([0-9]+),\ ([0-9]+) ]]; then start=${BASH_REMATCH[1]} end=${BASH_REMATCH[2]} new_start=$((end + 1)) + # 检查是否已处理过该batch if [[ -f "$LAST_BATCH_FILE" ]] && grep -q "$batch" "$LAST_BATCH_FILE"; then sleep 30 continue fi echo "$batch" > "$LAST_BATCH_FILE" - warn "检测到 missing trie node 错误区块,自动跳过到 $new_start 并重启节点..." + warn "检测到missing trie node错误区块,自动跳过到 $new_start 并重启节点..." + # 修改 config.json jq ".chain.snapshot_sync.starting_sub_id = $new_start" "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE" + # 重启docker服务 cd "$COMPOSE_DIR" docker-compose restart node sleep 60 @@ -801,7 +709,7 @@ monitor_and_skip_trie_error() { done } -# 启动守护进程(后台运行) -monitor_and_skip_trie_error & +# 主流程一开始就启动守护进程(后台运行) +nohup bash -c 'monitor_and_skip_trie_error' >/dev/null 2>&1 & exit 0 From 4783d573a3121ff49f63f9235c52b643810c596f Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 22 Oct 2025 16:51:16 +0800 Subject: [PATCH 03/14] window_ritual --- Ritual/window_ritual | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Ritual/window_ritual diff --git a/Ritual/window_ritual b/Ritual/window_ritual new file mode 100644 index 0000000..c83bc95 --- /dev/null +++ b/Ritual/window_ritual @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e # 出错即退出 +set -u # 使用未定义变量时报错 + +PROJECT_DIR="$HOME/infernet-container-starter/deploy" +COMPOSE_FILE="$PROJECT_DIR/docker-compose.yaml" + +echo "🚀 切换到部署目录:$PROJECT_DIR" +cd "$PROJECT_DIR" || { echo "❌ 目录不存在:$PROJECT_DIR"; exit 1; } + +# === 更新 deploy/config.json 配置参数 === +echo "ℹ️ 正在更新配置文件 config.json 中的参数..." +jq '.chain.snapshot_sync.batch_size = 10 | .chain.snapshot_sync.starting_sub_id = 262500 | .chain.snapshot_sync.retry_delay = 60' config.json > config.json.tmp +mv config.json.tmp config.json + +echo "✅ 已更新以下参数:" +echo "- batch_size: 10" +echo "- starting_sub_id: 262500" +echo "- retry_delay: 60" + +echo "🔍 检查并更新 docker-compose.yml 中的 depends_on 设置..." + +# 检查并修改 depends_on 行 +if grep -q 'depends_on: \[ redis, infernet-anvil \]' "$COMPOSE_FILE"; then + sed -i.bak 's/depends_on: \[ redis, infernet-anvil \]/depends_on: [ redis ]/' "$COMPOSE_FILE" + echo "✅ 已修改 depends_on 配置。备份文件保存在:docker-compose.yml.bak" +else + echo "✅ depends_on 配置已正确,无需修改。" +fi + +echo "🧹 停止并清理当前 Docker Compose 服务..." +docker compose down || { echo "⚠️ docker compose down 执行失败,继续执行下一步..."; } + +echo "⚙️ 启动指定服务:node、redis、fluentbit" +while true; do + docker compose up node redis fluentbit && break + echo "⚠️ 服务启动失败,5秒后重试..." + sleep 5 + done From 89c77c92b4aa8bce14a8177e406bccf3f2357684 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Tue, 24 Feb 2026 11:17:49 +0800 Subject: [PATCH 04/14] Create Tashi --- Tashi | 1 + 1 file changed, 1 insertion(+) create mode 100644 Tashi diff --git a/Tashi b/Tashi new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Tashi @@ -0,0 +1 @@ + From 260708e2d814e39475753b19d5b79721be6c7802 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Tue, 24 Feb 2026 11:18:37 +0800 Subject: [PATCH 05/14] Update Tashi --- Tashi | 1548 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1548 insertions(+) diff --git a/Tashi b/Tashi index 8b13789..e3684d5 100644 --- a/Tashi +++ b/Tashi @@ -1 +1,1549 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2155,SC2181 +IMAGE_TAG='ghcr.io/tashigg/tashi-depin-worker:0' + +TROUBLESHOOT_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#troubleshooting' +MANUAL_UPDATE_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#manual-update' + +DOCKER_ROOTLESS_LINK='https://docs.docker.com/engine/install/linux-postinstall/' +PODMAN_ROOTLESS_LINK='https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md' + +RUST_LOG='info,tashi_depin_worker=debug,tashi_depin_common=debug' + +AGENT_PORT=39065 + +# Color codes +GREEN="\e[32m" +RED="\e[31m" +YELLOW="\e[33m" +RESET="\e[0m" +CHECKMARK="${GREEN}✓${RESET}" +CROSSMARK="${RED}✗${RESET}" +WARNING="${YELLOW}⚠${RESET}" + +STYLE_BOLD=$(tput bold) +STYLE_NORMAL=$(tput sgr0) + +WARNINGS=0 +ERRORS=0 + +# Logging function (with level and timestamps if `LOG_EXPANDED` is set to a truthy value) +log() { + # Allow the message to be piped for heredocs + local message="${2:-$(cat)}" + + if [[ "${LOG_EXPANDED:-0}" -ne 0 ]]; then + local level="$1" + local timestamp=$(date +"%Y-%m-%d %H:%M:%S") + + printf "[%s] [%s] %b\n" "${timestamp}" "${level}" "${message}" 1>&2 + else + printf "%b\n" "$message" + fi +} + +make_bold() { + # Allows heredoc expansion with pipes + local s="${1:-$(cat)}" + + printf "%s%s%s" "$STYLE_BOLD" "${s}" "$STYLE_NORMAL" +} + +# Print a blank line for visual separation. +horizontal_line() { + WIDTH=${COLUMNS:-$(tput cols)} + FILL_CHAR='-' + + # Prints a zero-length string but specifies it should be `$COLUMNS` wide, so the `printf` command pads it with blanks. + # We then use `tr` to replace those blanks with our padding character of choice. + printf '\n%*s\n\n' "$WIDTH" '' | tr ' ' "$FILL_CHAR" +} + +# munch args +POSITIONAL_ARGS=() + +SUBCOMMAND=install + +while [[ $# -gt 0 ]]; do + case $1 in + --ignore-warnings) + IGNORE_WARNINGS=y + ;; + -y | --yes) + YES=1 + ;; + --auto-update) + AUTO_UPDATE=y + ;; + --image-tag=*) + IMAGE_TAG="${1#"--image-tag="}" + ;; + --install) + SUBCOMMAND=install + ;; + --update) + SUBCOMMAND=update + ;; + -*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + ;; + esac + + shift +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +# Detect OS safely +detect_os() { + OS=$( + # shellcheck disable=SC1091 + source /etc/os-release >/dev/null 2>&1 + echo "${ID:-unknown}" + ) + if [[ "$OS" == "unknown" && "$(uname -s)" == "Darwin" ]]; then + OS="macos" + fi +} + +# Suggest package installation securely +suggest_install() { + local package=$1 + case "$OS" in + debian | ubuntu) echo " sudo apt update && sudo apt install -y $package" ;; + fedora) echo " sudo dnf install -y $package" ;; + arch) echo " sudo pacman -S --noconfirm $package" ;; + opensuse) echo " sudo zypper install -y $package" ;; + macos) echo " brew install $package" ;; + *) echo " Please install '$package' manually for your OS." ;; + esac +} + +# Resolve commands dynamically +NPROC_CMD=$(command -v nproc || echo "") +GREP_CMD=$(command -v grep || echo "") +DF_CMD=$(command -v df || echo "") + +# Check if a command exists +check_command() { + command -v "$1" >/dev/null 2>&1 +} + +# Platform Check +check_platform() { + PLATFORM_ARG='' + + local arch=$(uname -m) + + # Bash on MacOS doesn't support `@(pattern-list)` apparently? + if [[ "$arch" == "amd64" || "$arch" == "x86_64" ]]; then + log "INFO" "Platform Check: ${CHECKMARK} supported platform $arch" + elif [[ "$OS" == "macos" && "$arch" == arm64 ]]; then + # Ensure Apple Silicon runs the container as x86_64 using Rosetta + PLATFORM_ARG='--platform linux/amd64' + + log "WARNING" "Platform Check: ${WARNING} unsupported platform $arch" + log "INFO" <<-EOF + MacOS Apple Silicon is not currently supported, but the worker can still run through the Rosetta compatibility layer. + Performance and earnings will be less than a native node. + You may be prompted to install Rosetta when the worker node starts. + EOF + ((WARNINGS++)) + else + log "ERROR" "Platform Check: ${CROSSMARK} unsupported platform $arch" + log "INFO" "Join the Tashi Discord to request support for your system." + ((ERRORS++)) + return + fi +} + +# CPU Check +check_cpu() { + case "$OS" in + "macos") + threads=$(sysctl -n hw.ncpu) + ;; + *) + if [[ -z "$NPROC_CMD" ]]; then + log "WARNING" "'nproc' not found. Install coreutils:" + suggest_install "coreutils" + ((ERRORS++)) + return + fi + threads=$("$NPROC_CMD") + ;; + esac + + if [[ "$threads" -ge 4 ]]; then + log "INFO" "CPU Check: ${CHECKMARK} Found $threads threads (>= 4 recommended)" + elif [[ "$threads" -ge 2 ]]; then + log "WARNING" "CPU Check: ${WARNING} Found $threads threads (>= 2 required, 4 recommended)" + ((WARNINGS++)) + else + log "ERROR" "CPU Check: ${CROSSMARK} Only $threads threads found (Minimum: 2 required)" + ((ERRORS++)) + fi +} + +# Memory Check +check_memory() { + if [[ -z "$GREP_CMD" ]]; then + log "ERROR" "Memory Check: ${WARNING} 'grep' not found. Install grep:" + suggest_install "grep" + ((ERRORS++)) + return + fi + + case "$OS" in + "macos") + total_mem_bytes=$(sysctl -n hw.memsize) + total_mem_kb=$((total_mem_bytes / 1024)) + ;; + *) + total_mem_kb=$("$GREP_CMD" MemTotal /proc/meminfo | awk '{print $2}') + ;; + esac + + total_mem_gb=$((total_mem_kb / 1024 / 1024)) + + if [[ "$total_mem_gb" -ge 4 ]]; then + log "INFO" "Memory Check: ${CHECKMARK} Found ${total_mem_gb}GB RAM (>= 4GB recommended)" + elif [[ "$total_mem_gb" -ge 2 ]]; then + log "WARNING" "Memory Check: ${WARNING} Found ${total_mem_gb}GB RAM (>= 2GB required, 4GB recommended)" + ((WARNINGS++)) + else + log "ERROR" "Memory Check: ${CROSSMARK} Only ${total_mem_gb}GB RAM found (Minimum: 2GB required)" + ((ERRORS++)) + fi +} + +# Disk Space Check +check_disk() { + case "$OS" in + "macos") + available_disk_kb=$( + "$DF_CMD" -kcI 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + total_mem_bytes=$(sysctl -n hw.memsize) + ;; + *) + available_disk_kb=$( + "$DF_CMD" -kx tmpfs --total 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + ;; + esac + + available_disk_gb=$((available_disk_kb / 1024 / 1024)) + + if [[ "$available_disk_gb" -ge 20 ]]; then + log "INFO" "Disk Space Check: ${CHECKMARK} Found ${available_disk_gb}GB free (>= 20GB required)" + else + log "ERROR" "Disk Space Check: ${CROSSMARK} Only ${available_disk_gb}GB free space (Minimum: 20GB required)" + ((ERRORS++)) + fi +} + +# Docker or Podman Check +check_container_runtime() { + # 首先检测操作系统 + detect_os + + if check_command "docker"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Docker is installed" + CONTAINER_RT=docker + + # 检查 Docker 是否运行 + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is running" + else + log "WARNING" "Docker Runtime Check: ${WARNING} Docker is installed but not running" + + # 根据操作系统启动 Docker + if [[ "$OS" == "macos" ]]; then + log "INFO" "Attempting to start Docker Desktop..." + open -a Docker 2>/dev/null || { + log "WARNING" "Failed to start Docker Desktop automatically" + log "INFO" "Please manually start Docker Desktop and press Enter to continue..." + read -r + } + + # 等待 Docker 启动 + log "INFO" "Waiting for Docker Desktop to start..." + local waited=0 + local max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + break + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if ! docker info >/dev/null 2>&1; then + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker failed to start after ${max_wait} seconds" + log "INFO" "Please ensure Docker Desktop is running and try again" + ((ERRORS++)) + fi + else + # Linux 系统尝试启动 Docker 服务 + if command -v systemctl >/dev/null 2>&1; then + log "INFO" "Attempting to start Docker service..." + if sudo systemctl start docker 2>/dev/null; then + sleep 3 + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker service failed to start" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Failed to start Docker service" + log "INFO" "Please manually start Docker service: sudo systemctl start docker" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker is not running and cannot be started automatically" + ((ERRORS++)) + fi + fi + fi + elif check_command "podman"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Podman is installed" + CONTAINER_RT=podman + else + log "WARNING" "Container Runtime Check: ${WARNING} Neither Docker nor Podman is installed." + + # 尝试安装 Docker + if [[ "$OS" == "macos" ]]; then + # 检查 Homebrew 是否安装 + if ! check_command "brew"; then + log "INFO" "Homebrew is not installed. Installing Homebrew first..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + log "ERROR" "Failed to install Homebrew" + ((ERRORS++)) + return + } + # 设置 Homebrew 环境 + if [[ -f "/opt/homebrew/bin/brew" ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -f "/usr/local/bin/brew" ]]; then + eval "$(/usr/local/bin/brew shellenv)" + fi + fi + + log "INFO" "Installing Docker Desktop via Homebrew..." + local install_attempt=0 + local max_attempts=5 + while [ $install_attempt -lt $max_attempts ]; do + if brew install --cask docker; then + log "INFO" "🚀 Docker Desktop installation successful!" + log "INFO" "Please manually start Docker Desktop: open -a Docker" + log "INFO" "Please wait for Docker Desktop to start completely (this may take a few minutes)." + read -p "Press Enter to continue (ensure Docker Desktop is running)..." + + # 尝试自动启动 Docker Desktop + open -a Docker 2>/dev/null || true + + # 等待 Docker 启动 + log "INFO" "Waiting for Docker Desktop to start..." + local waited=0 + local max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + CONTAINER_RT=docker + return + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if docker info >/dev/null 2>&1; then + CONTAINER_RT=docker + return + else + log "WARNING" "Docker installed but not running. Please start Docker Desktop manually." + ((ERRORS++)) + return + fi + else + install_attempt=$((install_attempt + 1)) + if [ $install_attempt -lt $max_attempts ]; then + log "WARNING" "Docker Desktop installation failed, retrying... ($install_attempt/$max_attempts)" + sleep 10 + else + log "ERROR" "Docker Desktop installation failed after $max_attempts attempts" + ((ERRORS++)) + fi + fi + done + else + # Linux 系统提示安装 + log "ERROR" "Container Runtime Check: ${CROSSMARK} Docker is not installed" + suggest_install "docker.io" + ((ERRORS++)) + fi + fi +} + +# Check network connectivity & NAT status +check_internet() { + # Step 1: Confirm Public Internet Access (No ICMP Required) + if curl -s --head --connect-timeout 3 https://google.com | grep "HTTP" >/dev/null 2>&1; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + elif wget --spider --timeout=3 --quiet https://google.com; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + else + log "ERROR" "Internet Connectivity: ${CROSSMARK} No internet access detected!" + ((ERRORS++)) + fi +} + +get_local_ip() { + if [[ "$OS" == "macos" ]]; then + LOCAL_IP=$(ifconfig -l | xargs -n1 ipconfig getifaddr) + elif check_command hostname; then + LOCAL_IP=$(hostname -I | awk '{print $1}') + elif check_command ip; then + # Use `ip route` to find what IP address connects to the internet + LOCAL_IP=$(ip route get '1.0.0.0' | grep -Po "src \K(\S+)") + fi +} + +get_public_ip() { + PUBLIC_IP=$(curl -s https://api.ipify.org || wget -qO- https://api.ipify.org) +} + +check_nat() { + local nat_message=$( + cat <<-EOF + If this device is not accessible from the Internet, some DePIN services will be disabled; + earnings may be less than a publicly accessible node. + + For maximum earning potential, ensure UDP port $AGENT_PORT is forwarded to this device. + Consult your router’s manual or contact your Internet Service Provider for details. + EOF + ); + + # Step 2: Get local & public IP + get_local_ip + get_public_ip + + if [[ -z "$LOCAL_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine local IP." + log "WARNING" "$nat_message" + return + fi + + if [[ -z "$PUBLIC_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine public IP." + log "WARNING" "$nat_message" + return + fi + + # Step 3: Determine NAT Type + if [[ "$LOCAL_IP" == "$PUBLIC_IP" ]]; then + log "INFO" "NAT Check: ${CHECKMARK} Open NAT / Publicly accessible (Public IP: $PUBLIC_IP)" + return + fi + + log "WARNING" "NAT Check: NAT detected (Local: $LOCAL_IP, Public: $PUBLIC_IP)" + log "WARNING" "$nat_message" +} + +check_root_required() { + # Docker and Podman on Mac run a Linux VM. The client commands outside the VM do not require root. + if [[ "$OS" == "macos" ]]; then + SUDO_CMD='' + log "INFO" "Privilege Check: ${CHECKMARK} Root privileges are not needed on MacOS" + return + fi + + if [[ "$CONTAINER_RT" == "docker" ]]; then + if (groups "$USER" | grep docker >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User is in 'docker' group." + log "INFO" "Worker container can be started without needing superuser privileges." + elif [[ -w "$DOCKER_HOST" ]] || [[ -w "/var/run/docker.sock" ]]; then + log "INFO" "Privilege Check: ${CHECKMARK} User has access to the Docker daemon socket." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo -g docker" + log "WARNING" "Privilege Check: ${WARNING} User is not in 'docker' group." + log "WARNING" <<-EOF + ${WARNING} 'docker run' command will be executed using '${SUDO_CMD}' + You may be prompted for your password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $DOCKER_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + elif [[ "$CONTAINER_RT" == "podman" ]]; then + # Check that the user and their login group are assigned substitute ID ranges + if (grep "^$USER:" /etc/subuid >/dev/null) && (grep "^$(id -gn):" /etc/subgid >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User can create Podman containers without root." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo" + log "WARNING" "Privilege Check: ${WARNING} User cannot create rootless Podman containers." + log "WARNING" <<-EOF + ${WARNING} 'podman run' command will be executed using '${SUDO_CMD}' + You may be prompted for your sudo password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $PODMAN_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + fi +} + +prompt_auto_updates() { + log "INFO" <<-EOF + Your DePIN worker will require periodic updates to ensure that it keeps up with new features and bug fixes. + Out-of-date workers may be excluded from the DePIN network and be unable to complete jobs or earn rewards. + + We recommend enabling automatic updates, which take place entirely in the container + and do not make any changes to your system. + + Otherwise, you will need to check the worker logs regularly to see when a new update is available, + and apply the update manually.\n + EOF + + # 默认启用自动更新(自动选择 Y) + log "INFO" "Automatic updates enabled (default: yes)." + AUTO_UPDATE=y + + # Blank line + echo "" +} + +prompt() { + local prompt="${1?}" + local variable="${2?}" + + # read -p in zsh is "read from coprocess", whatever that means + printf "%b" "$prompt" + + # Always read from TTY even if piped in + read -r "${variable?}" /dev/null 2>&1; then + return 1 + fi + + # 使用 python3 解密(直接传递变量) + python3 -c " +import base64 +import sys + +encrypted = '$encrypted' +key = 'RL_SWARM_2024' + +try: + decoded = base64.b64decode(encrypted) + result = bytearray() + key_bytes = key.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + print(result.decode('utf-8')) +except Exception as e: + sys.exit(1) +" 2>/dev/null +} + +# 获取设备唯一标识符(完全照搬 upload_devices.sh 的 get_mac_serial 函数) +get_device_code() { + local serial="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + # ===== macOS: Use hardware serial number ===== + # Method 1: Use system_profiler (recommended, most reliable) + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + + # Method 2: If method 1 fails, use ioreg + if [ -z "$serial" ]; then + if command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + fi + + # Method 3: If both methods fail, try sysctl + if [ -z "$serial" ]; then + if command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + fi + else + # ===== Linux: Use machine-id / hardware UUID ===== + # Prefer /etc/machine-id (system unique identifier) + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + + # Second try DMI hardware UUID + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + + # Third try hostnamectl machine ID + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + + echo "$serial" +} + +# 获取当前用户名(完全照搬 upload_devices.sh 的 get_current_user 函数) +get_current_user() { + local user="" + + # Prefer $USER environment variable + if [ -n "$USER" ]; then + user="$USER" + # Second use whoami + elif command -v whoami >/dev/null 2>&1; then + user=$(whoami) + # Last try id command + elif command -v id >/dev/null 2>&1; then + user=$(id -un) + fi + + echo "$user" +} + +# 构建 JSON(完全照搬 upload_devices.sh 的 build_json 函数) +build_json() { + local customer_name="$1" + local device_code="$2" + + echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" +} + +# 获取服务器配置(支持加密配置,参考 upload_devices.sh) +get_server_config() { + # 加密的默认配置(与 upload_devices.sh 保持一致) + local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" + local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + + # 优先级:环境变量 > 加密默认值 + if [ -n "$TASHI_SERVER_URL" ]; then + SERVER_URL="$TASHI_SERVER_URL" + log "INFO" "Using SERVER_URL from TASHI_SERVER_URL environment variable" + elif [ -n "$SERVER_URL" ]; then + # 使用 SERVER_URL 环境变量 + log "INFO" "Using SERVER_URL from SERVER_URL environment variable" + : + else + # 使用加密的默认值并解密 + log "INFO" "Decrypting SERVER_URL from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default SERVER_URL" + SERVER_URL="" + else + # 使用 decrypt_string 函数(更可靠) + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") + fi + fi + + if [ -n "$TASHI_API_KEY" ]; then + API_KEY="$TASHI_API_KEY" + log "INFO" "Using API_KEY from TASHI_API_KEY environment variable" + elif [ -n "$API_KEY" ]; then + # 使用 API_KEY 环境变量 + log "INFO" "Using API_KEY from API_KEY environment variable" + : + else + # 使用加密的默认值并解密 + log "INFO" "Decrypting API_KEY from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default API_KEY" + API_KEY="" + else + # 使用 decrypt_string 函数(更可靠) + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") + fi + fi + + # 导出为全局变量供其他函数使用 + export SERVER_URL API_KEY + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + log "INFO" "Server configuration not available, device check will be skipped" + fi +} + +# 检查设备状态(完全照搬 upload_devices.sh 的 check_device_status 函数) +# Return value semantics (server convention): +# 1 -> Enabled (normal), function returns 0, script continues +# 0 -> Disabled/not found: return 2 (for caller to identify) +# Other/network error -> return 1 (treated as exception) +check_device_status() { + local device_code="$1" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + # 未配置服务器信息,跳过检查 + return 0 + fi + + # 完全照搬 upload_devices.sh 的实现(不使用超时,与原始脚本保持一致) + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + # Network error or abnormal return value + # 在安装脚本中,网络错误也返回 1,让调用者决定如何处理 + return 1 + fi +} + +# 上传设备信息(完全照搬 upload_devices.sh 的逻辑,不使用超时) +upload_device_info() { + local device_code="$1" + local customer_name="$2" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 1 + fi + + # Build JSON(完全照搬 upload_devices.sh) + local devices_json + devices_json=$(build_json "$customer_name" "$device_code") + + # Send request (silent)(完全照搬 upload_devices.sh,不使用超时) + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + # Check if upload is successful (based on response body) + # Support multiple success indicators(完全照搬 upload_devices.sh): + # 1. code: \"0000\" + # 2. success_count > 0 + # 3. Traditional success:true or status:\"success\" or code:200 + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + return 0 + else + return 1 + fi +} + +# 设备检测和上传主函数(完全照搬 auto_run.sh 和 upload_devices.sh 的逻辑) +# 设备检测和上传主函数(完全照搬 upload_devices.sh 的 main 函数逻辑) +setup_device_check() { + # 获取服务器配置(必须在开始时调用) + get_server_config + + # 检查必需参数(完全照搬 upload_devices.sh) + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + log "WARNING" "Server URL or API key not configured, skipping device check" + return 0 + fi + + # 状态文件路径(完全照搬 upload_devices.sh) + local STATE_FILE="$HOME/.device_registered" + if [ -z "$HOME" ] && [ -n "$USERPROFILE" ]; then + # Windows + STATE_FILE="$USERPROFILE/.device_registered" + elif [ -z "$HOME" ] && [ -z "$USERPROFILE" ]; then + # Fallback to current directory + STATE_FILE=".device_registered" + fi + + # 迁移逻辑(完全照搬 upload_devices.sh) + local SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + local OLD_STATE_FILE="$SCRIPT_DIR/.device_registered" + if [ -f "$OLD_STATE_FILE" ] && [ ! -f "$STATE_FILE" ]; then + # Old file exists in project directory, but new location doesn't exist + # Copy to home directory for compatibility + cp "$OLD_STATE_FILE" "$STATE_FILE" 2>/dev/null || true + fi + + # Get Mac serial number(完全照搬 upload_devices.sh) + local DEVICE_CODE + DEVICE_CODE=$(get_device_code) + + if [ -z "$DEVICE_CODE" ]; then + log "WARNING" "Could not get device code, skipping device check" + return 0 + fi + + # If previously uploaded successfully and device code matches, skip re-upload, only do status check + # (完全照搬 upload_devices.sh) + if [ -f "$STATE_FILE" ]; then + local SAVED_CODE + SAVED_CODE=$(grep '^device_code=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-) + if [ -n "$SAVED_CODE" ] && [ "$SAVED_CODE" = "$DEVICE_CODE" ]; then + # 只检查状态,不重新上传(完全照搬 upload_devices.sh) + if check_device_status "$DEVICE_CODE"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + log "ERROR" "Device is disabled. Installation aborted." + return 2 + else + # 网络错误,继续执行 + return 0 + fi + fi + fi + fi + + # Get current username as default value(完全照搬 upload_devices.sh) + local DEFAULT_CUSTOMER + DEFAULT_CUSTOMER=$(get_current_user) + + # Prompt user to enter customer name(完全照搬 upload_devices.sh) + local CUSTOMER_NAME="" + if [ "${SKIP_CONFIRM:-false}" != "true" ]; then + # 交互式提示(不做输出重定向,让用户看到提示) + read -p "请输入客户名称 (直接回车使用默认: $DEFAULT_CUSTOMER): " CUSTOMER_NAME + else + # If skip confirm, use environment variable or default value + CUSTOMER_NAME="${CUSTOMER_NAME:-$DEFAULT_CUSTOMER}" + fi + + # If user didn't enter or input is empty, use default username(完全照搬 upload_devices.sh) + if [ -z "$CUSTOMER_NAME" ]; then + CUSTOMER_NAME="$DEFAULT_CUSTOMER" + fi + + # Clean whitespace(完全照搬 upload_devices.sh) + CUSTOMER_NAME=$(echo "$CUSTOMER_NAME" | xargs) + + if [ -z "$CUSTOMER_NAME" ]; then + log "ERROR" "Customer name cannot be empty. Installation aborted." + return 1 + fi + + # Build JSON(完全照搬 upload_devices.sh) + local devices_json + devices_json=$(build_json "$CUSTOMER_NAME" "$DEVICE_CODE") + + # Send request (silent)(完全照搬 upload_devices.sh) + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + # Check if upload is successful (based on response body) + # Support multiple success indicators(完全照搬 upload_devices.sh): + # 1. code: \"0000\" + # 2. success_count > 0 + # 3. Traditional success:true or status:\"success\" or code:200 + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + # After upload success, check device status(完全照搬 upload_devices.sh) + if check_device_status "$DEVICE_CODE"; then + # If execution reaches here, it means: + # 1. Upload successful + # 2. Device status is enabled + # Record successful upload info, subsequent runs will only do status check, no re-upload + # (完全照搬 upload_devices.sh) + { + echo "device_code=$DEVICE_CODE" + echo "customer_name=$CUSTOMER_NAME" + echo "uploaded_at=$(date '+%Y-%m-%d %H:%M:%S')" + } > "$STATE_FILE" 2>/dev/null || true + + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + log "ERROR" "Device is disabled after registration. Installation aborted." + return 2 + else + # 网络错误,但上传成功,继续执行 + return 0 + fi + fi + else + log "ERROR" "Failed to upload device information. Installation aborted." + return 1 + fi +} + +# ============ 修改点1: 完全绕过设备检查 ============ +# 覆盖原函数,直接返回成功 +setup_device_check() { + log "INFO" "Device check bypassed (modified script)" + return 0 +} + +check_and_stop_existing_container() { + # 检查容器是否存在(运行中或已停止) + if ${CONTAINER_RT} ps -a --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Found existing container: ${CONTAINER_NAME}" + + # 检查容器是否在运行 + if ${CONTAINER_RT} ps --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Stopping running container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} stop "$CONTAINER_NAME" >/dev/null 2>&1 + fi + + # 删除容器(无论是否运行) + log "INFO" "Removing existing container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} rm "$CONTAINER_NAME" >/dev/null 2>&1 + + log "INFO" "Existing container removed: ${CHECKMARK}" + fi +} + +install() { + # 设备检测已在脚本开始时完成,这里直接继续安装流程 + # 检查并停止已存在的容器 + check_and_stop_existing_container + + log "INFO" "Installing worker. The commands being run will be printed for transparency.\n" + + log "INFO" "Starting worker in interactive setup mode.\n" + + local setup_cmd=$(make_setup_cmd) + + sh -c "set -ex; $setup_cmd" + + local exit_code=$? + + echo "" + + if [[ $exit_code -eq 130 ]]; then + log "INFO" "Worker setup cancelled. You may re-run this script at any time." + exit 0 + elif [[ $exit_code -ne 0 ]]; then + log "ERROR" "Setup failed ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + exit 1 + fi + + local run_cmd=$(make_run_cmd) + + sh -c "set -ex; $run_cmd" + + exit_code=$? + + echo "" + + if [[ $exit_code -ne 0 ]]; then + log "ERROR" "Worker failed to start ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + + # 检查是否是授权文件缺失的问题 + local logs_output=$(docker logs "$CONTAINER_NAME" 2>&1 | tail -5) + if echo "$logs_output" | grep -q "node_auth.txt\|No such file or directory"; then + echo "" + log "ERROR" "Authorization file not found. This usually means:" + log "ERROR" " 1. The interactive setup was not completed" + log "ERROR" " 2. The authorization token was not entered" + log "ERROR" "" + log "ERROR" "Please re-run this script and ensure you complete the interactive setup" + log "ERROR" "and enter the authorization token when prompted." + fi + fi +} + +update() { + log "INFO" "Updating worker. The commands being run will be printed for transparency.\n" + + local container_old="$CONTAINER_NAME" + local container_new="$CONTAINER_NAME-new" + + local create_cmd=$(make_run_cmd "" "create" "$container_new" "$container_old") + + # Execute this whole next block as `sudo` if necessary. + # Piping means the sub-process reads line by line and can tell us right where it failed. + # Note: when referring to local shell variables *in* the script, be sure to escape: \$foo + ${SUDO_CMD+"$SUDO_CMD "}bash <<-EOF + set -x + + ($CONTAINER_RT inspect "$CONTAINER_NAME-old" >/dev/null 2>&1) + + if [ \$? -eq 0 ]; then + echo "$CONTAINER_NAME-old already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi + + ($CONTAINER_RT inspect "$container_new" >/dev/null 2>&1) + + if [ \$? -eq 0 ]; then + echo "$container_new already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi + + set -ex + + $create_cmd + $CONTAINER_RT stop $container_old + $CONTAINER_RT start $container_new + $CONTAINER_RT rename $container_old $CONTAINER_NAME-old + $CONTAINER_RT rename $container_new $CONTAINER_NAME + + echo -n "Would you like to delete $CONTAINER_NAME-old? (Y/n) " + read -r choice &2 <<-EOF + + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #-:::::::::::::::::::::::::::::=%@@@@@@@@@@@@@@%=:::::::::::::::::::::::::::::-# + @@*::::::::::::::::::::::::::::::+%@@@@@@@@@@%+::::::::::::::::::::::::::::::*@@ + @@@@+::::::::::::::::::::::::::::::+%@@@@@@%+::::::::::::::::::::::::::::::+@@@@ + @@@@@%=::::::::::::::::::::::::::::::+%@@%+::::::::::::::::::::::::::::::=%@@@@@ + @@@@@@@#-::::::::::::::::::::::::::::::@@::::::::::::::::::::::::::::::-#@@@@@@@ + @@@@@@@@@*:::::::::::::::::::::::::::::@@:::::::::::::::::::::::::::::*@@@@@@@@@ + @@@@@@@@@@%+:::::::::::::::::::::::::::@@:::::::::::::::::::::::::::+%@@@@@@@@@@ + @@@@@@@@@@@@%++++++++++++-:::::::::::::@@:::::::::::::-++++++++++++%@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@#-:::::::::::@@:::::::::::-#@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@*::::::::::@@::::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:::::::::@@:::::::::#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:::::::@@:::::::+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::::@@::::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::@@::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#=@@=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + + EOF +} + +setup_monitor_script() { + # 优先使用用户目录,避免权限问题 + local monitor_script="$HOME/.local/bin/monitor_tashi.sh" + local log_file="/tmp/tashi_monitor.log" + + # 确保用户目录存在 + mkdir -p "$HOME/.local/bin" 2>/dev/null || true + + # 如果用户目录创建失败,尝试系统目录(需要 sudo) + if [[ ! -d "$HOME/.local/bin" ]] || [[ ! -w "$HOME/.local/bin" ]]; then + monitor_script="/usr/local/bin/monitor_tashi.sh" + fi + + # 创建监控脚本 + if [[ "$monitor_script" == "/usr/local/bin/monitor_tashi.sh" ]]; then + # 需要 sudo 权限 + ${SUDO_CMD:+"$SUDO_CMD "}bash -c "cat > '$monitor_script'" << 'MONITOR_EOF' +#!/bin/bash +CONTAINER_NAME="tashi-depin-worker" +LOG_FILE="/tmp/tashi_monitor.log" + +# 检查容器是否存在 +if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查容器是否在运行 +if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查最近 5 分钟是否有断开连接 +if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then + # 检查是否在最近 2 分钟内已经重连成功 + if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then + echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null + docker restart "$CONTAINER_NAME" >/dev/null 2>&1 + fi +fi +MONITOR_EOF + ${SUDO_CMD:+"$SUDO_CMD "}chmod +x "$monitor_script" 2>/dev/null || true + else + # 用户目录,不需要 sudo + cat > "$monitor_script" << 'MONITOR_EOF' +#!/bin/bash +CONTAINER_NAME="tashi-depin-worker" +LOG_FILE="/tmp/tashi_monitor.log" + +# 检查容器是否存在 +if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查容器是否在运行 +if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查最近 5 分钟是否有断开连接 +if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then + # 检查是否在最近 2 分钟内已经重连成功 + if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then + echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null + docker restart "$CONTAINER_NAME" >/dev/null 2>&1 + fi +fi +MONITOR_EOF + chmod +x "$monitor_script" 2>/dev/null || true + fi + + # 验证脚本是否创建成功 + if [[ ! -f "$monitor_script" ]]; then + log "WARN" "Failed to create monitor script at $monitor_script" + return 1 + fi + + # 添加到 crontab(每 5 分钟检查一次) + local cron_entry="*/5 * * * * $monitor_script >/dev/null 2>&1" + + # 检查是否已存在,如果存在但路径不同,先删除旧的 + local existing_cron=$(crontab -l 2>/dev/null | grep "monitor_tashi.sh" || true) + if [[ -n "$existing_cron" ]] && [[ "$existing_cron" != *"$monitor_script"* ]]; then + # 删除旧的 crontab 条目 + crontab -l 2>/dev/null | grep -v "monitor_tashi.sh" | crontab - 2>/dev/null || true + fi + + # 如果不存在,添加新的 + if ! crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - 2>/dev/null || true + fi + + # 验证 crontab 是否添加成功 + if crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + return 0 + else + log "WARN" "Failed to add monitor script to crontab" + return 1 + fi +} + +post_install() { + echo "" + + log "INFO" "Worker is running: ${CHECKMARK}" + + echo "" + + local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" + local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" + + log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" + log "INFO" "To view the logs of your worker: '$logs_cmd'" + + # 设置监控脚本 + setup_monitor_script + + # 创建桌面快捷方式 + create_desktop_shortcut +} + +create_desktop_shortcut() { + local desktop_path="" + + # 检测桌面路径 + if [[ -n "$HOME" ]]; then + # macOS + if [[ "$OS" == "macos" ]]; then + desktop_path="$HOME/Desktop" + # Linux - 尝试常见的桌面路径 + elif [[ -d "$HOME/Desktop" ]]; then + desktop_path="$HOME/Desktop" + elif [[ -d "$HOME/桌面" ]]; then + desktop_path="$HOME/桌面" + fi + fi + + if [[ -z "$desktop_path" || ! -d "$desktop_path" ]]; then + log "INFO" "Desktop directory not found, skipping shortcut creation." + return + fi + + local shortcut_file="$desktop_path/Tashi.command" + + # 创建快捷方式文件 + cat > "$shortcut_file" <<'SCRIPT_EOF' +#!/bin/bash + +# Tashi DePIN Worker restart script + +# 设置颜色 +GREEN="\033[32m" +RED="\033[31m" +YELLOW="\033[33m" +RESET="\033[0m" + +# 配置 +CONTAINER_NAME="tashi-depin-worker" +AUTH_VOLUME="tashi-depin-worker-auth" +AUTH_DIR="/home/worker/auth" +AGENT_PORT=39065 +IMAGE_TAG="ghcr.io/tashigg/tashi-depin-worker:0" +PLATFORM_ARG="--platform linux/amd64" +RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" + +# ============ 设备检测函数 ============ +# 获取设备唯一标识 +get_device_code() { + local device_code="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then + device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then + device_code=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + else + if [ -f /etc/machine-id ]; then + device_code=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + fi + + echo "$device_code" +} + +# 检查设备状态 +check_device_status() { + local device_code="$1" + local server_url="${TASHI_SERVER_URL:-}" + local api_key="${TASHI_API_KEY:-}" + + if [ -z "$server_url" ] || [ -z "$api_key" ]; then + # 尝试使用外部脚本 + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + # 使用外部脚本检查(静默模式) + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + return 2 # 设备被禁用 + else + return 0 # 网络错误,允许继续 + fi + fi + else + # 未配置,允许继续 + return 0 + fi + fi + + local status + status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 0 # 网络错误,允许继续 + fi +} + +perform_device_check() { + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi + fi + + local device_code=$(get_device_code) + if [ -z "$device_code" ]; then + return 0 + fi + + if check_device_status "$device_code"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi +} + +# 切换到脚本所在目录 +cd "$(dirname "$0")" || exit 1 + +# 清屏 +clear + +perform_device_check >/dev/null 2>&1 + +if docker stop "$CONTAINER_NAME" >/dev/null 2>&1; then + docker rm "$CONTAINER_NAME" >/dev/null 2>&1 +fi + +if docker run -d \ + -p "$AGENT_PORT:$AGENT_PORT" \ + -p 127.0.0.1:9000:9000 \ + --mount type=volume,src="$AUTH_VOLUME",dst="$AUTH_DIR" \ + --name "$CONTAINER_NAME" \ + -e RUST_LOG="$RUST_LOG" \ + --health-cmd='pgrep -f tashi-depin-worker || exit 1' \ + --health-interval=30s \ + --health-timeout=10s \ + --health-retries=3 \ + --restart=unless-stopped \ + --pull=always \ + $PLATFORM_ARG \ + "$IMAGE_TAG" \ + run "$AUTH_DIR" \ + --unstable-update-download-path /tmp/tashi-depin-worker; then + : +else + exit 1 +fi + +docker logs -f "$CONTAINER_NAME" +SCRIPT_EOF + + # 设置执行权限 + chmod +x "$shortcut_file" + + log "INFO" "Desktop shortcut created: $shortcut_file" +} + +# Detect OS before running checks +detect_os + +# ============ 修改点2: 完全跳过设备检查 ============ +# 直接注释掉原来的设备检查代码,不执行任何设备验证 +# log "INFO" "Checking device registration and authorization..." +# +# # 执行设备检测(完全照搬 auto_run.sh 的逻辑) +# setup_device_check +# device_check_rc=$? +# +# # 约定(完全照搬 auto_run.sh): +# # 0 -> 一切正常(已启用,可以继续) +# # 2 -> 设备被禁用或不存在(禁止继续运行) +# # 1/其它 -> 脚本异常(也禁止继续运行) +# log "INFO" "Device check function returned with code: $device_check_rc" +# +# # 根据返回码处理错误 +# if [ "$device_check_rc" -eq 2 ]; then +# log "ERROR" "Device check failed: Device is disabled or not authorized." +# log "INFO" "Please contact administrator to enable your device." +# exit 2 +# elif [ "$device_check_rc" -eq 1 ]; then +# log "ERROR" "Device check failed: Unable to register or verify device." +# log "INFO" "Please check your network connection and try again." +# exit 1 +# fi + +# 添加跳过提示 +log "INFO" "Device registration check has been bypassed (modified script version)" +log "INFO" "Continuing with Docker check..." + +# Check Docker (required for installation) +# This must be done before any other checks since Docker is essential +log "INFO" "Checking Docker installation and runtime..." +check_container_runtime + +# Run all checks +display_logo + +log "INFO" "Starting system checks..." + +echo "" + +check_platform +check_cpu +check_memory +check_disk +check_root_required +check_internet + +echo "" + +check_warnings + +horizontal_line + +# Integrated NAT check. This is separate from system requirements because most manually started worker nodes +# are expected to be behind some sort of NAT, so this is mostly informational. +check_nat + +horizontal_line + +prompt_auto_updates + +horizontal_line + +prompt_continue + +case "$SUBCOMMAND" in + install) install ;; + update) update ;; + *) + log "ERROR" "BUG: no handler for $($SUBCOMMAND)" + exit 1 +esac + +post_install From 025dcd80554fed1d5ef4132745dabac4acb802b8 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 25 Feb 2026 23:12:41 +0800 Subject: [PATCH 06/14] Create control.sh --- control.sh | 1 + 1 file changed, 1 insertion(+) create mode 100644 control.sh diff --git a/control.sh b/control.sh new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/control.sh @@ -0,0 +1 @@ + From 7d9696e2a6b9cb4106dd680890e2a056e8471304 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 25 Feb 2026 23:14:54 +0800 Subject: [PATCH 07/14] control.sh --- control.sh | 1498 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1498 insertions(+) diff --git a/control.sh b/control.sh index 8b13789..bbd4e7c 100644 --- a/control.sh +++ b/control.sh @@ -1 +1,1499 @@ +#!/bin/bash + +# 柔和色彩设置 +GREEN='\033[1;32m' # 柔和绿色 +BLUE='\033[1;36m' # 柔和蓝色 +RED='\033[1;31m' # 柔和红色 +YELLOW='\033[1;33m' # 柔和黄色 +NC='\033[0m' # 无颜色 + +# 日志文件设置 +LOG_FILE="$HOME/nexus.log" +MAX_LOG_SIZE=10485760 # 10MB,日志大小限制 + +# 检测操作系统 +OS=$(uname -s) +case "$OS" in + Darwin) OS_TYPE="macOS" ;; + Linux) + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + OS_TYPE="Ubuntu" + else + OS_TYPE="Linux" + fi + else + OS_TYPE="Linux" + fi + ;; + *) echo -e "${RED}不支持的操作系统: $OS。本脚本仅支持 macOS 和 Ubuntu。${NC}" ; exit 1 ;; +esac + +# 检测 shell 并设置配置文件 +if [[ -n "$ZSH_VERSION" ]]; then + SHELL_TYPE="zsh" + CONFIG_FILE="$HOME/.zshrc" +elif [[ -n "$BASH_VERSION" ]]; then + SHELL_TYPE="bash" + CONFIG_FILE="$HOME/.bashrc" +else + echo -e "${RED}不支持的 shell。本脚本仅支持 bash 和 zsh。${NC}" + exit 1 +fi + +# 打印标题 +print_header() { + echo -e "${BLUE}=====================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}=====================================${NC}" +} + +# 检查命令是否存在 +check_command() { + if command -v "$1" &> /dev/null; then + echo -e "${GREEN}$1 已安装,跳过安装步骤。${NC}" + return 0 + else + echo -e "${RED}$1 未安装,开始安装...${NC}" + return 1 + fi +} + +# 配置 shell 环境变量,避免重复写入 +configure_shell() { + local env_path="$1" + local env_var="export PATH=$env_path:\$PATH" + if [[ -f "$CONFIG_FILE" ]] && grep -Fx "$env_var" "$CONFIG_FILE" > /dev/null; then + echo -e "${GREEN}环境变量已在 $CONFIG_FILE 中配置。${NC}" + else + echo -e "${BLUE}正在将环境变量添加到 $CONFIG_FILE...${NC}" + echo "$env_var" >> "$CONFIG_FILE" + echo -e "${GREEN}环境变量已添加到 $CONFIG_FILE。${NC}" + # 应用当前会话的更改 + source "$CONFIG_FILE" 2>/dev/null || echo -e "${RED}无法加载 $CONFIG_FILE,请手动运行 'source $CONFIG_FILE'。${NC}" + fi +} + +# 日志轮转 +rotate_log() { + if [[ -f "$LOG_FILE" ]]; then + if [[ "$OS_TYPE" == "macOS" ]]; then + FILE_SIZE=$(stat -f %z "$LOG_FILE" 2>/dev/null) + else + FILE_SIZE=$(stat -c %s "$LOG_FILE" 2>/dev/null) + fi + if [[ $FILE_SIZE -ge $MAX_LOG_SIZE ]]; then + mv "$LOG_FILE" "${LOG_FILE}.$(date +%F_%H-%M-%S).bak" + echo -e "${YELLOW}日志文件已轮转,新日志将写入 $LOG_FILE${NC}" + fi + fi +} + +# 安装 Homebrew(macOS 和非 Ubuntu Linux) +install_homebrew() { + print_header "检查 Homebrew 安装" + if check_command brew; then + return + fi + echo -e "${BLUE}在 $OS_TYPE 上安装 Homebrew...${NC}" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + echo -e "${RED}安装 Homebrew 失败,请检查网络连接或权限。${NC}" + exit 1 + } + if [[ "$OS_TYPE" == "macOS" ]]; then + configure_shell "/opt/homebrew/bin" + else + configure_shell "$HOME/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/bin" + if ! check_command gcc; then + echo -e "${BLUE}在 Linux 上安装 gcc(Homebrew 依赖)...${NC}" + if command -v yum &> /dev/null; then + sudo yum groupinstall 'Development Tools' || { + echo -e "${RED}安装 gcc 失败,请手动安装 Development Tools。${NC}" + exit 1 + } + else + echo -e "${RED}不支持的包管理器,请手动安装 gcc。${NC}" + exit 1 + fi + fi + fi +} + +# 安装基础依赖(仅 Ubuntu) +install_dependencies() { + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + print_header "安装基础依赖工具" + echo -e "${BLUE}更新 apt 包索引并安装必要工具...${NC}" + sudo apt-get update -y + sudo apt-get install -y curl jq screen build-essential || { + echo -e "${RED}安装依赖工具失败,请检查网络连接或权限。${NC}" + exit 1 + } + fi +} + +# 安装 CMake +install_cmake() { + print_header "检查 CMake 安装" + if check_command cmake; then + return + fi + echo -e "${BLUE}正在安装 CMake...${NC}" + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + sudo apt-get install -y cmake || { + echo -e "${RED}安装 CMake 失败,请检查网络连接或权限。${NC}" + exit 1 + } + else + brew install cmake || { + echo -e "${RED}安装 CMake 失败,请检查 Homebrew 安装。${NC}" + exit 1 + } + fi +} + +# 安装 Protobuf +install_protobuf() { + print_header "检查 Protobuf 安装" + if check_command protoc; then + return + fi + echo -e "${BLUE}正在安装 Protobuf...${NC}" + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + sudo apt-get install -y protobuf-compiler || { + echo -e "${RED}安装 Protobuf 失败,请检查网络连接或权限。${NC}" + exit 1 + } + else + brew install protobuf || { + echo -e "${RED}安装 Protobuf 失败,请检查 Homebrew 安装。${NC}" + exit 1 + } + fi +} + +# 安装 Rust +install_rust() { + print_header "检查 Rust 安装" + if check_command rustc; then + return + fi + echo -e "${BLUE}正在安装 Rust...${NC}" + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y || { + echo -e "${RED}安装 Rust 失败,请检查网络连接。${NC}" + exit 1 + } + source "$HOME/.cargo/env" 2>/dev/null || echo -e "${RED}无法加载 Rust 环境,请手动运行 'source ~/.cargo/env'。${NC}" + configure_shell "$HOME/.cargo/bin" +} + +# 配置 Rust RISC-V 目标 +configure_rust_target() { + print_header "检查 Rust RISC-V 目标" + if rustup target list --installed | grep -q "riscv32i-unknown-none-elf"; then + echo -e "${GREEN}RISC-V 目标 (riscv32i-unknown-none-elf) 已安装,跳过。${NC}" + return + fi + echo -e "${BLUE}为 Rust 添加 RISC-V 目标...${NC}" + rustup target add riscv32i-unknown-none-elf || { + echo -e "${RED}添加 RISC-V 目标失败,请检查 Rust 安装。${NC}" + exit 1 + } +} + +# 日志函数 +log() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S %Z')] $1" | tee -a "$LOG_FILE" + rotate_log +} + +# 退出时的清理函数 +cleanup_exit() { + log "${YELLOW}收到退出信号,正在清理 Nexus 节点进程...${NC}" + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 先获取窗口信息,再终止进程,最后关闭窗口 + log "${BLUE}正在获取 Nexus 相关窗口信息...${NC}" + + # 获取包含nexus的窗口ID + nexus_window_id=$(osascript -e 'tell app "Terminal" to id of first window whose name contains "node-id"' 2>/dev/null || echo "") + if [[ -n "$nexus_window_id" ]]; then + log "${BLUE}发现 Nexus 窗口ID: $nexus_window_id,准备关闭...${NC}" + else + log "${YELLOW}未找到 Nexus 窗口,第一次启动,跳过关闭操作${NC}" + fi + + # 现在终止进程 + log "${BLUE}正在终止 Nexus 节点进程...${NC}" + + # 查找并终止 nexus-network 和 nexus-cli 进程 + local pids=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') + if [[ -n "$pids" ]]; then + log "${BLUE}发现进程: $pids,正在终止...${NC}" + for pid in $pids; do + kill -TERM "$pid" 2>/dev/null || true + sleep 1 + # 如果进程还在运行,强制终止 + if ps -p "$pid" > /dev/null 2>&1; then + kill -KILL "$pid" 2>/dev/null || true + fi + done + fi + + # 等待进程完全终止 + sleep 2 + + # 清理 screen 会话(如果存在) + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + else + # 非 macOS: 清理 screen 会话 + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + fi + + # 查找并终止 nexus-network 和 nexus-cli 进程 + log "${BLUE}正在查找并清理残留的 Nexus 进程...${NC}" + PIDS=$(ps aux | grep -E "nexus-cli|nexus-network" | grep -v grep | awk '{print $2}' | tr '\n' ' ' | xargs echo -n) + log "${BLUE}ps 找到的进程: '$PIDS'${NC}" + + if [[ -z "$PIDS" ]]; then + log "${YELLOW}ps 未找到进程,尝试 pgrep...${NC}" + PIDS=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ' | xargs echo -n) + log "${BLUE}pgrep 找到的进程: '$PIDS'${NC}" + fi + + if [[ -n "$PIDS" ]]; then + for pid in $PIDS; do + if ps -p "$pid" > /dev/null 2>&1; then + log "${BLUE}正在终止 Nexus 节点进程 (PID: $pid)...${NC}" + kill -9 "$pid" 2>/dev/null || log "${RED}无法终止 PID $pid 的进程,请检查进程状态。${NC}" + fi + done + else + log "${GREEN}未找到残留的 nexus-network 或 nexus-cli 进程。${NC}" + fi + + # 额外清理:查找可能的子进程 + log "${BLUE}检查是否有子进程残留...${NC}" + local child_pids=$(pgrep -P $(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') 2>/dev/null | tr '\n' ' ') + if [[ -n "$child_pids" ]]; then + log "${BLUE}发现子进程: $child_pids,正在清理...${NC}" + for pid in $child_pids; do + kill -9 "$pid" 2>/dev/null || true + done + fi + + # 等待所有进程完全清理 + sleep 5 + + # 最后才关闭窗口(确保所有进程都已终止) + if [[ "$OS_TYPE" == "macOS" ]]; then + log "${BLUE}正在关闭 Nexus 节点终端窗口...${NC}" + + if [[ -n "$nexus_window_id" ]]; then + # 直接关闭找到的nexus窗口 + log "${BLUE}关闭 Nexus 窗口 (ID: $nexus_window_id)...${NC}" + osascript -e "tell application \"Terminal\" to close window id $nexus_window_id saving no" 2>/dev/null || true + sleep 2 + log "${BLUE}窗口关闭完成${NC}" + else + log "${YELLOW}没有找到 Nexus 窗口,跳过关闭操作${NC}" + fi + fi + + log "${GREEN}清理完成,脚本退出。${NC}" + exit 0 +} + +# 重启时的清理函数 +cleanup_restart() { + # 重启前清理日志 + if [[ -f "$LOG_FILE" ]]; then + rm -f "$LOG_FILE" + echo -e "${YELLOW}已清理旧日志文件 $LOG_FILE${NC}" + fi + log "${YELLOW}准备重启节点,开始清理流程...${NC}" + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 先获取窗口信息,再终止进程,最后关闭窗口 + log "${BLUE}正在获取 Nexus 相关窗口信息...${NC}" + + # 获取包含nexus的窗口ID + nexus_window_id=$(osascript -e 'tell app "Terminal" to id of first window whose name contains "node-id"' 2>/dev/null || echo "") + if [[ -n "$nexus_window_id" ]]; then + log "${BLUE}发现 Nexus 窗口ID: $nexus_window_id,准备关闭...${NC}" + else + log "${YELLOW}未找到 Nexus 窗口,第一次启动,跳过关闭操作${NC}" + fi + + # 现在终止进程 + log "${BLUE}正在终止 Nexus 节点进程...${NC}" + + # 查找并终止 nexus-network 和 nexus-cli 进程 + local pids=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') + if [[ -n "$pids" ]]; then + log "${BLUE}发现进程: $pids,正在终止...${NC}" + for pid in $pids; do + kill -TERM "$pid" 2>/dev/null || true + sleep 1 + # 如果进程还在运行,强制终止 + if ps -p "$pid" > /dev/null 2>&1; then + kill -KILL "$pid" 2>/dev/null || true + fi + done + fi + + # 等待进程完全终止 + sleep 2 + + # 清理 screen 会话(如果存在) + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + else + # 非 macOS: 清理 screen 会话 + if screen -list | grep -q "nexus_node"; then + log "${BLUE}正在终止 nexus_node screen 会话...${NC}" + screen -S nexus_node -X quit 2>/dev/null || log "${RED}无法终止 screen 会话,请检查权限或会话状态。${NC}" + fi + fi + + # 查找并终止 nexus-network 和 nexus-cli 进程 + log "${BLUE}正在查找并清理残留的 Nexus 进程...${NC}" + PIDS=$(ps aux | grep -E "nexus-cli|nexus-network" | grep -v grep | awk '{print $2}' | tr '\n' ' ' | xargs echo -n) + log "${BLUE}ps 找到的进程: '$PIDS'${NC}" + + if [[ -z "$PIDS" ]]; then + log "${YELLOW}ps 未找到进程,尝试 pgrep...${NC}" + PIDS=$(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ' | xargs echo -n) + log "${BLUE}pgrep 找到的进程: '$PIDS'${NC}" + fi + + if [[ -n "$PIDS" ]]; then + for pid in $PIDS; do + if ps -p "$pid" > /dev/null 2>&1; then + log "${BLUE}正在终止 Nexus 节点进程 (PID: $pid)...${NC}" + kill -9 "$pid" 2>/dev/null || log "${RED}无法终止 PID $pid 的进程,请检查进程状态。${NC}" + fi + done + else + log "${GREEN}未找到残留的 nexus-network 或 nexus-cli 进程。${NC}" + fi + + # 额外清理:查找可能的子进程 + log "${BLUE}检查是否有子进程残留...${NC}" + local child_pids=$(pgrep -P $(pgrep -f "nexus-cli\|nexus-network" | tr '\n' ' ') 2>/dev/null | tr '\n' ' ') + if [[ -n "$child_pids" ]]; then + log "${BLUE}发现子进程: $child_pids,正在清理...${NC}" + for pid in $child_pids; do + kill -9 "$pid" 2>/dev/null || true + done + fi + + # 等待所有进程完全清理 + sleep 5 + + # 最后才关闭窗口(确保所有进程都已终止) + if [[ "$OS_TYPE" == "macOS" ]]; then + log "${BLUE}正在关闭 Nexus 节点终端窗口...${NC}" + + if [[ -n "$nexus_window_id" ]]; then + # 直接关闭找到的nexus窗口 + log "${BLUE}关闭 Nexus 窗口 (ID: $nexus_window_id)...${NC}" + osascript -e "tell application \"Terminal\" to close window id $nexus_window_id saving no" 2>/dev/null || true + sleep 2 + log "${BLUE}窗口关闭完成${NC}" + else + log "${YELLOW}没有找到 Nexus 窗口,跳过关闭操作${NC}" + fi + fi + + log "${GREEN}清理完成,准备重启节点。${NC}" +} + +trap 'cleanup_exit' SIGINT SIGTERM SIGHUP + +# 安装或更新 Nexus CLI +install_nexus_cli() { + local attempt=1 + local max_attempts=3 + local success=false + while [[ $attempt -le $max_attempts ]]; do + log "${BLUE}正在安装/更新 Nexus CLI(第 $attempt/$max_attempts 次)...${NC}" + if curl -s https://cli.nexus.xyz/ | sh &>/dev/null; then + log "${GREEN}Nexus CLI 安装/更新成功!${NC}" + success=true + break + else + log "${YELLOW}第 $attempt 次安装/更新 Nexus CLI 失败。${NC}" + ((attempt++)) + sleep 2 + fi + done + # 确保配置文件存在,如果没有就生成并写入 PATH 变量 + if [[ ! -f "$CONFIG_FILE" ]]; then + echo "export PATH=\"$HOME/.cargo/bin:\$PATH\"" > "$CONFIG_FILE" + log "${YELLOW}未检测到 $CONFIG_FILE,已自动生成并写入 PATH 变量。${NC}" + fi + # 更新CLI后加载环境变量 + source "$CONFIG_FILE" 2>/dev/null && log "${GREEN}已自动加载 $CONFIG_FILE 环境变量。${NC}" || log "${YELLOW}未能自动加载 $CONFIG_FILE,请手动执行 source $CONFIG_FILE。${NC}" + # 额外加载.zshrc确保环境变量生效 + if [[ -f "$HOME/.zshrc" ]]; then + source "$HOME/.zshrc" 2>/dev/null && log "${GREEN}已额外加载 ~/.zshrc 环境变量。${NC}" || log "${YELLOW}未能加载 ~/.zshrc,请手动执行 source ~/.zshrc。${NC}" + fi + if [[ "$success" == false ]]; then + log "${RED}Nexus CLI 安装/更新失败 $max_attempts 次,将尝试使用当前版本运行节点。${NC}" + fi + + # 等待一下确保安装完成 + sleep 3 + + # 验证安装结果 + if command -v nexus-network &>/dev/null; then + log "${GREEN}nexus-network 版本:$(nexus-network --version 2>/dev/null)${NC}" + elif command -v nexus-cli &>/dev/null; then + log "${GREEN}nexus-cli 版本:$(nexus-cli --version 2>/dev/null)${NC}" + else + log "${RED}未找到 nexus-network 或 nexus-cli,无法运行节点。${NC}" + log "${YELLOW}尝试重新安装...${NC}" + # 再次尝试安装 + if curl -s https://cli.nexus.xyz/ | sh; then + log "${GREEN}重新安装成功!${NC}" + sleep 2 + # 重新验证 + if command -v nexus-network &>/dev/null || command -v nexus-cli &>/dev/null; then + log "${GREEN}验证通过,可以继续运行节点${NC}" + else + log "${RED}重新安装后仍然无法找到命令,退出脚本${NC}" + exit 1 + fi + else + log "${RED}重新安装失败,退出脚本${NC}" + exit 1 + fi + fi + + # 首次安装后生成仓库hash,避免首次运行时等待 + if [[ ! -f "$HOME/.nexus/last_commit" ]]; then + log "${BLUE}首次安装,正在生成仓库hash记录...${NC}" + local repo_url="https://github.com/nexus-xyz/nexus-cli.git" + local current_commit=$(git ls-remote --heads "$repo_url" main 2>/dev/null | cut -f1) + + if [[ -n "$current_commit" ]]; then + mkdir -p "$HOME/.nexus" + echo "$current_commit" > "$HOME/.nexus/last_commit" + log "${GREEN}已记录当前仓库版本: ${current_commit:0:8}${NC}" + else + log "${YELLOW}无法获取仓库信息,将在后续检测时创建${NC}" + fi + fi +} + +# 读取或设置 Node ID,添加 5 秒超时 +get_node_id() { + CONFIG_PATH="$HOME/.nexus/config.json" + if [[ -f "$CONFIG_PATH" ]]; then + CURRENT_NODE_ID=$(jq -r .node_id "$CONFIG_PATH" 2>/dev/null) + if [[ -n "$CURRENT_NODE_ID" && "$CURRENT_NODE_ID" != "null" ]]; then + log "${GREEN}检测到配置文件中的 Node ID:$CURRENT_NODE_ID${NC}" + # 使用 read -t 5 实现 5 秒超时,默认选择 y + echo -e "${BLUE}是否使用此 Node ID? (y/n, 默认 y,5 秒后自动继续): ${NC}" + use_old_id="" + read -t 5 -r use_old_id + use_old_id=${use_old_id:-y} # 默认 y + if [[ "$use_old_id" =~ ^[Nn]$ ]]; then + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + # 验证 Node ID(假设需要非空且只包含字母、数字、连字符) + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + jq --arg id "$NODE_ID_TO_USE" '.node_id = $id' "$CONFIG_PATH" > "$CONFIG_PATH.tmp" && mv "$CONFIG_PATH.tmp" "$CONFIG_PATH" + log "${GREEN}已更新 Node ID: $NODE_ID_TO_USE${NC}" + else + NODE_ID_TO_USE="$CURRENT_NODE_ID" + fi + else + log "${YELLOW}未检测到有效 Node ID,请输入新的 Node ID。${NC}" + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + mkdir -p "$HOME/.nexus" + echo "{\"node_id\": \"${NODE_ID_TO_USE}\"}" > "$CONFIG_PATH" + log "${GREEN}已写入 Node ID: $NODE_ID_TO_USE 到 $CONFIG_PATH${NC}" + fi + else + log "${YELLOW}未找到配置文件 $CONFIG_PATH,请输入 Node ID。${NC}" + read -rp "请输入新的 Node ID: " NODE_ID_TO_USE + if [[ -z "$NODE_ID_TO_USE" || ! "$NODE_ID_TO_USE" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "${RED}无效的 Node ID,请输入只包含字母、数字或连字符的 ID。${NC}" + exit 1 + fi + mkdir -p "$HOME/.nexus" + echo "{\"node_id\": \"${NODE_ID_TO_USE}\"}" > "$CONFIG_PATH" + log "${GREEN}已写入 Node ID: $NODE_ID_TO_USE 到 $CONFIG_PATH${NC}" + fi +} + +# 检测 GitHub 仓库更新 +check_github_updates() { + local repo_url="https://github.com/nexus-xyz/nexus-cli.git" + log "${BLUE}检查 Nexus CLI 仓库更新...${NC}" + + # 获取远程仓库最新提交 + local current_commit=$(git ls-remote --heads "$repo_url" main 2>/dev/null | cut -f1) + + if [[ -z "$current_commit" ]]; then + log "${YELLOW}无法获取远程仓库信息,跳过更新检测${NC}" + return 1 + fi + + if [[ -f "$HOME/.nexus/last_commit" ]]; then + local last_commit=$(cat "$HOME/.nexus/last_commit") + if [[ "$current_commit" != "$last_commit" ]]; then + log "${GREEN}检测到仓库更新!${NC}" + log "${BLUE}上次提交: ${last_commit:0:8}${NC}" + log "${BLUE}最新提交: ${current_commit:0:8}${NC}" + echo "$current_commit" > "$HOME/.nexus/last_commit" + return 0 # 有更新 + else + log "${GREEN}仓库无更新,当前版本: ${current_commit:0:8}${NC}" + return 1 # 无更新 + fi + else + log "${BLUE}首次运行,记录当前提交: ${current_commit:0:8}${NC}" + echo "$current_commit" > "$HOME/.nexus/last_commit" + return 0 # 首次运行 + fi +} + +# 启动节点 +start_node() { + log "${BLUE}正在启动 Nexus 节点 (Node ID: $NODE_ID_TO_USE)...${NC}" + rotate_log + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 新开终端窗口启动节点,并设置到指定位置 + log "${BLUE}在 macOS 中打开新终端窗口启动节点...${NC}" + + # 获取屏幕尺寸 + screen_info=$(system_profiler SPDisplaysDataType | grep Resolution | head -1 | awk '{print $2, $4}' | tr 'x' ' ') + if [[ -n "$screen_info" ]]; then + read -r screen_width screen_height <<< "$screen_info" + else + screen_width=1920 + screen_height=1080 + fi + + # 计算窗口位置(与 startAll.sh 中 nexus 位置完全一致) + spacing=20 + upper_height=$(((screen_height/2) - (2*spacing))) + lower_height=$(((screen_height/2) - (2*spacing))) + lower_y=$((upper_height + (2*spacing))) + + # 设置窗口位置:距离左边界30px + lower_item_width=$(((screen_width - spacing) / 2)) # 窗口宽度 + nexus_ritual_height=$((lower_height - 30)) + nexus_ritual_y=$((lower_y + 5)) + nexus_x=30 # 距离左边界30px + + # 启动节点并设置窗口位置和大小(103x31) + osascript < /dev/null; then + log "${GREEN}Nexus 节点已在新终端窗口中启动${NC}" + else + log "${YELLOW}nexus-network 启动失败,尝试用 nexus-cli 启动...${NC}" + # 使用相同的窗口位置和大小设置(103x31) + osascript < /dev/null; then + log "${GREEN}Nexus 节点已通过 nexus-cli 在新终端窗口中启动${NC}" + else + log "${RED}启动失败,将在下次更新检测时重试${NC}" + return 1 + fi + fi + else + # 非 macOS: 使用 screen 启动(保持原有逻辑) + log "${BLUE}在 $OS_TYPE 中使用 screen 启动节点...${NC}" + screen -dmS nexus_node bash -c "nexus-network start --node-id '${NODE_ID_TO_USE}' >> $LOG_FILE 2>&1" + sleep 2 + if screen -list | grep -q "nexus_node"; then + log "${GREEN}Nexus 节点已在 screen 会话(nexus_node)中启动,日志输出到 $LOG_FILE${NC}" + else + log "${YELLOW}nexus-network 启动失败,尝试用 nexus-cli 启动...${NC}" + screen -dmS nexus_node bash -c "nexus-cli start --node-id '${NODE_ID_TO_USE}' >> $LOG_FILE 2>&1" + sleep 2 + if screen -list | grep -q "nexus_node"; then + log "${GREEN}Nexus 节点已通过 nexus-cli 启动,日志输出到 $LOG_FILE${NC}" + else + log "${RED}启动失败,将在下次更新检测时重试${NC}" + return 1 + fi + fi + fi + + return 0 +} + +# 创建桌面快捷方式(参考 install_gensyn.sh) +create_desktop_shortcuts() { + if [[ "$OS_TYPE" != "macOS" ]]; then + return 0 + fi + + log "${BLUE}正在创建桌面快捷方式...${NC}" + + CURRENT_USER=$(whoami) + PROJECT_DIR="/Users/$CURRENT_USER/rl-swarm" + DESKTOP_DIR="/Users/$CURRENT_USER/Desktop" + mkdir -p "$DESKTOP_DIR" + + # 检查 rl-swarm 目录是否存在 + HAS_RL_SWARM=false + if [[ -d "$PROJECT_DIR" ]] && [[ -f "$PROJECT_DIR/nexus.sh" ]]; then + HAS_RL_SWARM=true + log "${GREEN}检测到 rl-swarm 目录,将使用 .sh 文件启动${NC}" + else + log "${YELLOW}未检测到 rl-swarm 目录,将直接执行命令启动${NC}" + fi + + # 创建 nexus.command + if [[ "$HAS_RL_SWARM" == true ]]; then + # 使用 rl-swarm 中的 nexus.sh + cat > "$DESKTOP_DIR/nexus.command" < "$DESKTOP_DIR/nexus.command" <<'NEXUS_DIRECT_EOF' +#!/bin/bash + +# 柔和色彩设置 +GREEN='\033[1;32m' +BLUE='\033[1;36m' +RED='\033[1;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# 日志文件设置 +LOG_FILE="$HOME/nexus.log" +MAX_LOG_SIZE=10485760 + +# 检测操作系统 +OS=$(uname -s) +case "$OS" in + Darwin) OS_TYPE="macOS" ;; + Linux) + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "$ID" == "ubuntu" ]]; then + OS_TYPE="Ubuntu" + else + OS_TYPE="Linux" + fi + else + OS_TYPE="Linux" + fi + ;; + *) echo -e "${RED}不支持的操作系统: $OS${NC}" ; exit 1 ;; +esac + +# 检测 shell 并设置配置文件 +if [[ -n "$ZSH_VERSION" ]]; then + CONFIG_FILE="$HOME/.zshrc" +elif [[ -n "$BASH_VERSION" ]]; then + CONFIG_FILE="$HOME/.bashrc" +else + echo -e "${RED}不支持的 shell${NC}" + exit 1 +fi + +# 日志函数 +log() { + echo -e "[$(date '+%Y-%m-%d %H:%M:%S %Z')] $1" | tee -a "$LOG_FILE" +} + +# 安装或更新 Nexus CLI +install_nexus_cli() { + local attempt=1 + local max_attempts=3 + while [[ $attempt -le $max_attempts ]]; do + log "${BLUE}正在安装/更新 Nexus CLI(第 $attempt/$max_attempts 次)...${NC}" + if curl -s https://cli.nexus.xyz/ | sh &>/dev/null; then + log "${GREEN}Nexus CLI 安装/更新成功!${NC}" + break + else + log "${YELLOW}第 $attempt 次安装/更新失败${NC}" + ((attempt++)) + sleep 2 + fi + done + + source "$CONFIG_FILE" 2>/dev/null || true + if [[ -f "$HOME/.zshrc" ]]; then + source "$HOME/.zshrc" 2>/dev/null || true + fi +} + +# 读取 Node ID +get_node_id() { + CONFIG_PATH="$HOME/.nexus/config.json" + if [[ -f "$CONFIG_PATH" ]]; then + NODE_ID=$(jq -r .node_id "$CONFIG_PATH" 2>/dev/null) + if [[ -z "$NODE_ID" || "$NODE_ID" == "null" ]]; then + echo -e "${RED}未找到 Node ID,请先运行部署脚本配置${NC}" + read -n 1 -s + exit 1 + fi + else + echo -e "${RED}未找到配置文件,请先运行部署脚本配置${NC}" + read -n 1 -s + exit 1 + fi +} + +# 启动节点 +start_nexus() { + log "${BLUE}正在启动 Nexus 节点 (Node ID: $NODE_ID)...${NC}" + + if [[ "$OS_TYPE" == "macOS" ]]; then + # macOS: 在新终端窗口启动 + osascript < "$DESKTOP_DIR/tashi.command" <<'TASHI_EOF' +#!/bin/bash + +# Tashi DePIN Worker restart script + +# 设置颜色 +GREEN="\033[32m" +RED="\033[31m" +YELLOW="\033[33m" +RESET="\033[0m" + +# 配置 +CONTAINER_NAME="tashi-depin-worker" +AUTH_VOLUME="tashi-depin-worker-auth" +AUTH_DIR="/home/worker/auth" +AGENT_PORT=39065 +IMAGE_TAG="ghcr.io/tashigg/tashi-depin-worker:0" +PLATFORM_ARG="--platform linux/amd64" +RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" + +# ============ 设备检测函数 ============ +# 获取设备唯一标识 +get_device_code() { + local device_code="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then + device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then + device_code=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + else + if [ -f /etc/machine-id ]; then + device_code=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + fi + + echo "$device_code" +} + +# 检查设备状态 +check_device_status() { + local device_code="$1" + local server_url="${TASHI_SERVER_URL:-}" + local api_key="${TASHI_API_KEY:-}" + + if [ -z "$server_url" ] || [ -z "$api_key" ]; then + # 尝试使用外部脚本 + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + # 使用外部脚本检查(静默模式) + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + return 2 # 设备被禁用 + else + return 0 # 网络错误,允许继续 + fi + fi + else + # 未配置,允许继续 + return 0 + fi + fi + + local status + status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 0 # 网络错误,允许继续 + fi +} + +perform_device_check() { + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi + fi + + local device_code=$(get_device_code) + if [ -z "$device_code" ]; then + return 0 + fi + + if check_device_status "$device_code"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi +} + +# 切换到脚本所在目录 +cd "$(dirname "$0")" || exit 1 + +# 清屏 +clear + +perform_device_check >/dev/null 2>&1 + +if docker stop "$CONTAINER_NAME" >/dev/null 2>&1; then + docker rm "$CONTAINER_NAME" >/dev/null 2>&1 +fi + +if docker run -d \ + -p "$AGENT_PORT:$AGENT_PORT" \ + -p 127.0.0.1:9000:9000 \ + --mount type=volume,src="$AUTH_VOLUME",dst="$AUTH_DIR" \ + --name "$CONTAINER_NAME" \ + -e RUST_LOG="$RUST_LOG" \ + --health-cmd='pgrep -f tashi-depin-worker || exit 1' \ + --health-interval=30s \ + --health-timeout=10s \ + --health-retries=3 \ + --restart=unless-stopped \ + --pull=always \ + $PLATFORM_ARG \ + "$IMAGE_TAG" \ + run "$AUTH_DIR" \ + --unstable-update-download-path /tmp/tashi-depin-worker; then + : +else + exit 1 +fi + +docker logs -f "$CONTAINER_NAME" +TASHI_EOF + chmod +x "$DESKTOP_DIR/tashi.command" + log "${GREEN}已创建 tashi.command${NC}" + + # 创建 startAll.command + if [[ "$HAS_RL_SWARM" == true ]] && [[ -f "$PROJECT_DIR/startAll.sh" ]]; then + # 使用 rl-swarm 中的 startAll.sh + cat > "$DESKTOP_DIR/startAll.command" < "$DESKTOP_DIR/startAll.command" <<'STARTALL_DIRECT_EOF' +#!/bin/bash + +# 1. 获取当前终端的窗口ID并关闭其他终端窗口(排除当前终端) +current_window_id=$(osascript -e 'tell app "Terminal" to id of front window') +echo "当前终端窗口ID: $current_window_id,正在保护此终端不被关闭..." + +osascript </dev/null 2>&1; then + screen_info=$(system_profiler SPDisplaysDataType | grep Resolution | head -1 | awk '{print $2, $4}' | tr 'x' ' ') + if [[ -n "$screen_info" ]]; then + read -r width height <<< "$screen_info" + x1=0 + y1=0 + x2=$width + y2=$height + echo "检测到屏幕尺寸: ${width}x${height}" + else + width=1920 + height=1080 + x1=0 + y1=0 + x2=1920 + y2=1080 + echo "使用默认屏幕尺寸: ${width}x${height}" + fi +else + width=1920 + height=1080 + x1=0 + y1=0 + x2=1920 + y2=1080 + echo "使用默认屏幕尺寸: ${width}x${height}" +fi + +# 窗口排列函数 +function arrange_window { + local title=$1 + local x=$2 + local y=$3 + local w=$4 + local h=$5 + + local right_x=$((x + w)) + local bottom_y=$((y + h)) + + echo "排列窗口 '$title': 位置($x, $y), 大小(${w}x${h}), 边界(${right_x}x${bottom_y})" + + if osascript -e "tell application \"Terminal\" to set bounds of first window whose name contains \"$title\" to {$x, $y, $right_x, $bottom_y}" 2>/dev/null; then + echo "✅ 窗口 '$title' 排列成功" + else + echo "⚠️ 窗口 '$title' 排列失败,尝试备用方法..." + local window_id=$(osascript -e "tell application \"Terminal\" to id of first window whose name contains \"$title\"" 2>/dev/null) + if [[ -n "$window_id" ]]; then + osascript -e "tell application \"Terminal\" to set bounds of window id $window_id to {$x, $y, $right_x, $bottom_y}" 2>/dev/null + echo "✅ 窗口 '$title' (ID: $window_id) 排列成功" + else + echo "❌ 无法找到窗口 '$title'" + fi + fi +} + +# 布局参数 +spacing=20 +upper_height=$((height/2-2*spacing)) +lower_height=$((height/2-2*spacing)) +lower_y=$((y1+upper_height+2*spacing)) + +# 上层布局 +upper_item_width=$(( (width-spacing)/2 )) + +# 下层布局(nexus、Ritual) +lower_item_width=$(( (width-spacing)/2 )) +nexus_ritual_height=$((lower_height-30)) +nexus_ritual_y=$((lower_y+5)) + +# wai宽度缩小1/2 +wai_width=$((upper_item_width/2)) +wai_height=$upper_height + +# 3. 启动Docker(不新建终端窗口) +echo "✅ 正在后台启动Docker..." +open -a Docker --background + +# 等待Docker完全启动 +echo "⏳ 等待Docker服务就绪..." +until docker info >/dev/null 2>&1; do sleep 1; done +sleep 30 + +# 4. 启动 Tashi(上层左侧,距离左边界30px,替换原来的 gensyn) +echo "📦 启动 Tashi 节点..." +osascript </dev/null; docker rm tashi-depin-worker 2>/dev/null; docker run -d -p 39065:39065 -p 127.0.0.1:9000:9000 --mount type=volume,src=tashi-depin-worker-auth,dst=/home/worker/auth --name tashi-depin-worker -e RUST_LOG='info,tashi_depin_worker=debug,tashi_depin_common=debug' --health-cmd='pgrep -f tashi-depin-worker || exit 1' --health-interval=30s --health-timeout=10s --health-retries=3 --restart=unless-stopped --pull=always --platform linux/amd64 ghcr.io/tashigg/tashi-depin-worker:0 run /home/worker/auth --unstable-update-download-path /tmp/tashi-depin-worker && docker logs -f tashi-depin-worker" +end tell +TASHI_SCRIPT +sleep 1 +arrange_window "tashi" $((x1+30)) $y1 $upper_item_width $upper_height + +# 5. 启动dria(上层右侧,向右偏移半个身位,宽度缩小1/2,高度不变) +echo "📦 启动 Dria 节点..." +osascript -e 'tell app "Terminal" to do script "cd ~ && dkn-compute-launcher start"' +sleep 1 +arrange_window "dkn-compute-launcher" $((x1+upper_item_width+spacing+upper_item_width/2)) $y1 $wai_width $wai_height + +# 6. 启动nexus(下层左侧,高度减小30px,向下移动5px) +echo "📦 启动 Nexus 节点..." +NEXUS_CONFIG="$HOME/.nexus/config.json" +if [[ -f "$NEXUS_CONFIG" ]]; then + NODE_ID=$(jq -r .node_id "$NEXUS_CONFIG" 2>/dev/null) + if [[ -n "$NODE_ID" && "$NODE_ID" != "null" ]]; then + osascript -e "tell app \"Terminal\" to do script \"cd ~ && nexus-network start --node-id $NODE_ID || nexus-cli start --node-id $NODE_ID\"" + sleep 1 + arrange_window "nexus" $x1 $nexus_ritual_y $lower_item_width $nexus_ritual_height + else + echo "⚠️ 未找到 Nexus Node ID" + fi +else + echo "⚠️ 未找到 Nexus 配置文件" +fi + +# Ritual 已删除,不再启动 + +echo "✅ 所有项目已启动完成!" +echo " - Docker已在后台运行" +echo " - Tashi 节点(替换 gensyn)" +echo " - Dria 节点" +echo " - Nexus 节点" +STARTALL_DIRECT_EOF + fi + chmod +x "$DESKTOP_DIR/startAll.command" + log "${GREEN}已创建 startAll.command${NC}" + + # 创建 clean_spotlight.command + if [[ "$HAS_RL_SWARM" == true ]] && [[ -f "$PROJECT_DIR/clean_spotlight.sh" ]]; then + # 使用 rl-swarm 中的 clean_spotlight.sh + cat > "$DESKTOP_DIR/clean_spotlight.command" < "$DESKTOP_DIR/clean_spotlight.command" <<'CLEAN_DIRECT_EOF' +#!/bin/bash + +# 设置错误处理 +set -e + +# 捕获中断信号 +trap 'echo -e "\n\033[33m⚠️ 脚本被中断,但终端将继续运行...\033[0m"; exit 0' INT TERM + +echo "🧹 正在清理 Spotlight 索引..." + +# macOS 清理 Spotlight 索引 +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "停止 Spotlight 索引..." + sudo mdutil -a -i off + + echo "删除 Spotlight 索引文件..." + sudo rm -rf /.Spotlight-V100 + + echo "重建 Spotlight 索引..." + sudo mdutil -a -i on + + echo "✅ Spotlight 索引清理完成" +else + echo "⚠️ 此脚本仅适用于 macOS" +fi + +echo "按任意键关闭此窗口..." +read -n 1 -s +CLEAN_DIRECT_EOF + fi + chmod +x "$DESKTOP_DIR/clean_spotlight.command" + log "${GREEN}已创建 clean_spotlight.command${NC}" + + log "${GREEN}所有桌面快捷方式已创建完成!${NC}" + + if [[ "$HAS_RL_SWARM" == false ]]; then + log "${YELLOW}提示:未检测到 rl-swarm 目录,快捷方式使用直接命令启动${NC}" + fi +} + +# Ritual 功能已删除,不再需要配置函数 + +# 更新 startAll.sh 以包含 Tashi 启动逻辑 +update_startall_script() { + if [[ "$OS_TYPE" != "macOS" ]]; then + return 0 + fi + + CURRENT_USER=$(whoami) + PROJECT_DIR="/Users/$CURRENT_USER/rl-swarm" + STARTALL_FILE="$PROJECT_DIR/startAll.sh" + + # 检查 rl-swarm 目录和 startAll.sh 是否存在 + if [[ ! -d "$PROJECT_DIR" ]]; then + log "${YELLOW}未找到 rl-swarm 目录: $PROJECT_DIR${NC}" + log "${YELLOW}startAll.command 已创建独立版本,不依赖 rl-swarm${NC}" + return 0 + fi + + if [[ ! -f "$STARTALL_FILE" ]]; then + log "${YELLOW}未找到 startAll.sh 文件: $STARTALL_FILE${NC}" + log "${YELLOW}startAll.command 已创建独立版本,不依赖 startAll.sh${NC}" + return 0 + fi + + log "${BLUE}正在更新 startAll.sh 以添加 Tashi 启动逻辑...${NC}" + + # 检查是否已经包含 Tashi + if grep -q "tashi\|Tashi\|TASHI" "$STARTALL_FILE"; then + log "${GREEN}startAll.sh 已包含 Tashi 启动逻辑,跳过更新${NC}" + return 0 + fi + + # 创建备份 + cp "$STARTALL_FILE" "${STARTALL_FILE}.backup.$(date +%Y%m%d_%H%M%S)" + log "${GREEN}已创建 startAll.sh 备份${NC}" + + # 查找 gensyn 相关代码并替换为 Tashi + # 根据 startAll.sh,gensyn 在 #4 位置(上层左侧,距离左边界30px) + + if grep -q "gensyn\|Gensyn\|GENSYN" "$STARTALL_FILE"; then + log "${BLUE}检测到 gensyn 代码,将替换为 Tashi...${NC}" + + # 使用 Python 或 awk 进行更安全的替换(避免 sed 引号问题) + python3 </dev/null; docker rm tashi-depin-worker 2>/dev/null; docker run -d -p 39065:39065 -p 127.0.0.1:9000:9000 --mount type=volume,src=tashi-depin-worker-auth,dst=/home/worker/auth --name tashi-depin-worker -e RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" --health-cmd="pgrep -f tashi-depin-worker || exit 1" --health-interval=30s --health-timeout=10s --health-retries=3 --restart=unless-stopped --pull=always --platform linux/amd64 ghcr.io/tashigg/tashi-depin-worker:0 run /home/worker/auth --unstable-update-download-path /tmp/tashi-depin-worker \&\& docker logs -f tashi-depin-worker|g' "$STARTALL_FILE" + sed -i '' 's/arrange_window "gensyn"/arrange_window "tashi"/g' "$STARTALL_FILE" + sed -i '' 's/# 4\. 启动gensyn/# 4. 启动 Tashi(替换原来的 gensyn)/g' "$STARTALL_FILE" + # 删除 Ritual 相关代码 + sed -i '' '/# 7\. 启动Ritual/,/arrange_window "Ritual"/d' "$STARTALL_FILE" + sed -i '' '/- Ritual/d' "$STARTALL_FILE" + else + sed -i 's|./gensyn.sh|docker stop tashi-depin-worker 2>/dev/null; docker rm tashi-depin-worker 2>/dev/null; docker run -d -p 39065:39065 -p 127.0.0.1:9000:9000 --mount type=volume,src=tashi-depin-worker-auth,dst=/home/worker/auth --name tashi-depin-worker -e RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" --health-cmd="pgrep -f tashi-depin-worker || exit 1" --health-interval=30s --health-timeout=10s --health-retries=3 --restart=unless-stopped --pull=always --platform linux/amd64 ghcr.io/tashigg/tashi-depin-worker:0 run /home/worker/auth --unstable-update-download-path /tmp/tashi-depin-worker \&\& docker logs -f tashi-depin-worker|g' "$STARTALL_FILE" + sed -i 's/arrange_window "gensyn"/arrange_window "tashi"/g' "$STARTALL_FILE" + sed -i 's/# 4\. 启动gensyn/# 4. 启动 Tashi(替换原来的 gensyn)/g' "$STARTALL_FILE" + # 删除 Ritual 相关代码 + sed -i '/# 7\. 启动Ritual/,/arrange_window "Ritual"/d' "$STARTALL_FILE" + sed -i '/- Ritual/d' "$STARTALL_FILE" + fi + log "${GREEN}已使用 sed 替换 gensyn 为 Tashi${NC}" + fi + else + log "${BLUE}未找到 gensyn 代码,将在 #4 位置添加 Tashi 启动逻辑...${NC}" + + # 使用 Python 在 #4 位置插入 Tashi 代码 + python3 </dev/null; docker rm tashi-depin-worker 2>/dev/null; docker run -d -p 39065:39065 -p 127.0.0.1:9000:9000 --mount type=volume,src=tashi-depin-worker-auth,dst=/home/worker/auth --name tashi-depin-worker -e RUST_LOG=\\"info,tashi_depin_worker=debug,tashi_depin_common=debug\\" --health-cmd=\\"pgrep -f tashi-depin-worker || exit 1\\" --health-interval=30s --health-timeout=10s --health-retries=3 --restart=unless-stopped --pull=always --platform linux/amd64 ghcr.io/tashigg/tashi-depin-worker:0 run /home/worker/auth --unstable-update-download-path /tmp/tashi-depin-worker && docker logs -f tashi-depin-worker"' +sleep 1 +arrange_window "tashi" \$((x1+30)) \$y1 \$upper_item_width \$upper_height +''' + +try: + with open(file_path, 'r', encoding='utf-8') as f: + lines = f.readlines() + + # 删除 Ritual 相关代码 + new_lines = [] + skip_ritual = False + for i, line in enumerate(lines): + if '# 7.' in line and '启动Ritual' in line: + skip_ritual = True + continue + if skip_ritual and 'arrange_window "Ritual"' in line: + skip_ritual = False + continue + if skip_ritual: + continue + if '- Ritual' in line: + continue + new_lines.append(line) + lines = new_lines + + # 查找 #4 或 # 4. 的位置 + insert_pos = -1 + for i, line in enumerate(lines): + if '# 4.' in line or '#4.' in line: + insert_pos = i + 1 + break + + # 如果找不到 #4,查找 # 6. 启动nexus 之前 + if insert_pos == -1: + for i, line in enumerate(lines): + if '# 6.' in line and '启动nexus' in line: + insert_pos = i + break + + # 如果还是找不到,在文件末尾添加 + if insert_pos == -1: + insert_pos = len(lines) + + # 插入 Tashi 代码 + lines.insert(insert_pos, tashi_code + '\n') + + with open(file_path, 'w', encoding='utf-8') as f: + f.writelines(lines) + + print("插入完成") + sys.exit(0) +except Exception as e: + print(f"插入失败: {e}", file=sys.stderr) + sys.exit(1) +PYTHON_INSERT_EOF + + if [[ $? -eq 0 ]]; then + log "${GREEN}已在 startAll.sh 中添加 Tashi 启动逻辑${NC}" + else + log "${YELLOW}Python 插入失败,请手动编辑 startAll.sh${NC}" + fi + fi + + log "${GREEN}已更新 startAll.sh${NC}" + log "${YELLOW}请检查 startAll.sh 文件,确保 Tashi 窗口位置和配置正确${NC}" +} + +# 主循环 +main() { + if [[ "$OS_TYPE" == "Ubuntu" ]]; then + install_dependencies + fi + if [[ "$OS_TYPE" == "macOS" || "$OS_TYPE" == "Linux" ]]; then + install_homebrew + fi + install_cmake + install_protobuf + install_rust + configure_rust_target + get_node_id + + # 创建桌面快捷方式(仅在 macOS 上) + if [[ "$OS_TYPE" == "macOS" ]]; then + create_desktop_shortcuts + + # Ritual 功能已删除,不再需要配置 + + # 更新 startAll.sh 以包含 Tashi 启动逻辑 + update_startall_script + fi + + # 首次启动节点 + log "${BLUE}首次启动 Nexus 节点...${NC}" + install_nexus_cli + cleanup_restart + if start_node; then + log "${GREEN}节点启动成功!${NC}" + else + log "${YELLOW}节点启动失败,将在下次更新检测时重试${NC}" + fi + + log "${BLUE}开始监控 GitHub 仓库更新...${NC}" + log "${BLUE}检测频率:每30分钟检查一次${NC}" + log "${BLUE}重启条件:仅在检测到仓库更新时重启${NC}" + + while true; do + # 每30分钟检查一次更新 + sleep 1800 + + if check_github_updates; then + log "${BLUE}检测到更新,准备重启节点...${NC}" + install_nexus_cli + cleanup_restart + if start_node; then + log "${GREEN}节点已成功重启!${NC}" + else + log "${YELLOW}节点重启失败,将在下次更新检测时重试${NC}" + fi + else + log "${BLUE}无更新,节点继续运行...${NC}" + fi + done +} + +main From 3566362d059124483d6734f11c2c513b5fc397cb Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 25 Feb 2026 23:42:37 +0800 Subject: [PATCH 08/14] deploy_optimai.sh --- Optimal/deploy_optimai.sh | 742 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 Optimal/deploy_optimai.sh diff --git a/Optimal/deploy_optimai.sh b/Optimal/deploy_optimai.sh new file mode 100644 index 0000000..24c3dfc --- /dev/null +++ b/Optimal/deploy_optimai.sh @@ -0,0 +1,742 @@ +#!/bin/bash +# OptimAI Core Node 安装脚本 + +# 简单的日志函数 +log() { + local level="$1" + local message="${2:-$(cat)}" + case "$level" in + "INFO") echo "$message" ;; + "WARNING") echo "⚠️ $message" ;; + "ERROR") echo "❌ $message" ;; + *) echo "$message" ;; + esac +} + +# 默认值 +SKIP_DEVICE_CHECK=false + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + --skip-device-check|--dev-mode) + SKIP_DEVICE_CHECK=true + echo "⚠️ 开发者模式:跳过设备检查" + shift + ;; + --help) + echo "使用方法: ./install.sh [选项]" + echo "选项:" + echo " --skip-device-check 跳过设备检查(开发者模式)" + echo " --dev-mode 等同于 --skip-device-check" + echo " --help 显示此帮助信息" + exit 0 + ;; + *) + echo "未知选项: $1" + echo "使用 --help 查看帮助" + exit 1 + ;; + esac +done + +echo "========================================" +echo " OptimAI Core Node 安装" +echo "========================================" +echo "" + +# 检测操作系统 +if [[ "$(uname)" != "Darwin" ]]; then + echo "❌ 此脚本仅支持 macOS 系统" + exit 1 +fi + +# ============ 设备检测函数 ============ +# 解密函数(参考 upload_devices.sh) +decrypt_string() { + local encrypted="$1" + + # 检查 python3 是否可用 + if ! command -v python3 >/dev/null 2>&1; then + return 1 + fi + + # 使用 python3 解密(直接传递变量) + python3 -c " +import base64 +import sys + +encrypted = '$encrypted' +key = 'RL_SWARM_2024' + +try: + decoded = base64.b64decode(encrypted) + result = bytearray() + key_bytes = key.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + print(result.decode('utf-8')) +except Exception as e: + sys.exit(1) +" 2>/dev/null +} + +# 获取设备唯一标识符 +get_device_code() { + local serial="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS: Use hardware serial number + # Method 1: Use system_profiler (recommended, most reliable) + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + + # Method 2: If method 1 fails, use ioreg + if [ -z "$serial" ]; then + if command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + fi + + # Method 3: If both methods fail, try sysctl + if [ -z "$serial" ]; then + if command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + fi + else + # Linux: Use machine-id / hardware UUID + # Prefer /etc/machine-id (system unique identifier) + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + + # Second try DMI hardware UUID + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + + # Third try hostnamectl machine ID + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + + echo "$serial" +} + +# 获取当前用户名 +get_current_user() { + local user="" + + # Prefer $USER environment variable + if [ -n "$USER" ]; then + user="$USER" + # Second use whoami + elif command -v whoami >/dev/null 2>&1; then + user=$(whoami) + # Last try id command + elif command -v id >/dev/null 2>&1; then + user=$(id -un) + fi + + echo "$user" +} + +# 构建 JSON +build_json() { + local customer_name="$1" + local device_code="$2" + + echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" +} + +# 获取服务器配置(支持加密配置) +get_server_config() { + # 加密的默认配置(与 upload_devices.sh 保持一致) + local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" + local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + + # 优先级:环境变量 > 加密默认值 + if [ -n "$OPTIMAI_SERVER_URL" ]; then + SERVER_URL="$OPTIMAI_SERVER_URL" + elif [ -n "$SERVER_URL" ]; then + # 使用 SERVER_URL 环境变量 + : + else + # 使用加密的默认值并解密 + if ! command -v python3 >/dev/null 2>&1; then + SERVER_URL="" + else + # 使用 decrypt_string 函数(更可靠) + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") + fi + fi + + if [ -n "$OPTIMAI_API_KEY" ]; then + API_KEY="$OPTIMAI_API_KEY" + elif [ -n "$API_KEY" ]; then + # 使用 API_KEY 环境变量 + : + else + # 使用加密的默认值并解密 + if ! command -v python3 >/dev/null 2>&1; then + API_KEY="" + else + # 使用 decrypt_string 函数(更可靠) + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") + fi + fi + + # 导出为全局变量供其他函数使用 + export SERVER_URL API_KEY +} + +# 检查设备状态 +# Return value semantics (server convention): +# 1 -> Enabled (normal), function returns 0, script continues +# 0 -> Disabled/not found: return 2 (for caller to identify) +# Other/network error -> return 1 (treated as exception) +check_device_status() { + local device_code="$1" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + # 未配置服务器信息,跳过检查 + return 0 + fi + + # 完全照搬 upload_devices.sh 的实现(不使用超时,与原始脚本保持一致) + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + # Network error or abnormal return value + # 在安装脚本中,网络错误也返回 1,让调用者决定如何处理 + return 1 + fi +} + +# 上传设备信息 +upload_device_info() { + local device_code="$1" + local customer_name="$2" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 1 + fi + + # Build JSON(完全照搬 upload_devices.sh) + local devices_json + devices_json=$(build_json "$customer_name" "$device_code") + + # Send request (silent)(完全照搬 upload_devices.sh,不使用超时) + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + # Check if upload is successful (based on response body) + # Support multiple success indicators(完全照搬 upload_devices.sh): + # 1. code: \"0000\" + # 2. success_count > 0 + # 3. Traditional success:true or status:\"success\" or code:200 + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + return 0 + else + return 1 + fi +} + +# 设备检测主函数(只检查设备是否存在,不注册新设备) +setup_device_check() { + # 获取服务器配置(必须在开始时调用) + get_server_config + + # 检查必需参数 + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + # 未配置服务器信息,跳过检查 + return 0 + fi + + # 获取设备码 + local DEVICE_CODE + DEVICE_CODE=$(get_device_code) + + if [ -z "$DEVICE_CODE" ]; then + # 无法获取设备码,拒绝安装 + echo "❌ 无法获取设备标识码,安装被拒绝" + return 1 + fi + + # 检查设备状态(只检查是否存在,不注册) + if check_device_status "$DEVICE_CODE"; then + # 设备存在且启用 + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + # 设备被禁用 + return 2 + else + # 设备不存在或网络错误 + echo "❌ 设备码不存在于服务器中,安装被拒绝" + return 1 + fi + fi +} + +# ============ 设备检测开始 ============ +# 根据开发者模式决定是否跳过设备检查 +if [ "$SKIP_DEVICE_CHECK" = "false" ]; then + # 执行设备检测 + setup_device_check + device_check_rc=$? + + # 约定: + # 0 -> 设备存在且启用,可以继续 + # 2 -> 设备被禁用,禁止继续运行 + # 1 -> 设备不存在或无法验证,禁止继续运行 + + # 根据返回码处理错误 + if [ "$device_check_rc" -eq 2 ]; then + echo "❌ 设备已被禁用" + echo " 请联系管理员启用您的设备" + exit 2 + elif [ "$device_check_rc" -eq 1 ]; then + echo "❌ 设备码不存在于服务器中" + echo " 此设备未授权,无法安装" + exit 1 + fi +else + echo "⚠️ 开发者模式:已跳过设备检查" +fi + +# 1. 检查是否已安装 +if command -v optimai-cli >/dev/null 2>&1; then + # 验证已安装的文件是否有效 + INSTALLED_PATH=$(which optimai-cli) + if [ -f "$INSTALLED_PATH" ] && file "$INSTALLED_PATH" 2>/dev/null | grep -qE "Mach-O|executable"; then + # 尝试执行版本命令验证 + if optimai-cli --version >/dev/null 2>&1; then + echo "✅ OptimAI CLI 已安装: $(optimai-cli --version 2>/dev/null || echo '未知版本')" + echo " 跳过下载和安装步骤" + else + echo "⚠️ 已安装的文件可能损坏,将重新下载..." + sudo rm -f "$INSTALLED_PATH" + fi + else + echo "⚠️ 已安装的文件无效,将重新下载..." + sudo rm -f "$INSTALLED_PATH" 2>/dev/null || true + fi +fi + +if ! command -v optimai-cli >/dev/null 2>&1; then + # 检测系统架构 + ARCH=$(uname -m) + echo "📥 下载 OptimAI CLI..." + echo " 系统架构: $ARCH" + + # 下载文件 + TEMP_FILE="/tmp/optimai-cli-$$" + curl -L -f https://optimai.network/download/cli-node/mac -o "$TEMP_FILE" + + if [ ! -f "$TEMP_FILE" ]; then + echo "❌ 下载失败" + exit 1 + fi + + # 验证文件完整性(检查文件大小和是否为有效的 Mach-O 文件) + FILE_SIZE=$(wc -c < "$TEMP_FILE" 2>/dev/null || echo "0") + if [ "$FILE_SIZE" -lt 1000000 ]; then + echo "❌ 下载的文件大小异常: $FILE_SIZE 字节,可能下载不完整" + rm -f "$TEMP_FILE" + exit 1 + fi + + # 验证是否为有效的 Mach-O 文件 + if ! file "$TEMP_FILE" 2>/dev/null | grep -qE "Mach-O|executable"; then + echo "❌ 下载的文件不是有效的可执行文件" + rm -f "$TEMP_FILE" + exit 1 + fi + + # 设置权限 + echo "🔧 设置权限..." + chmod +x "$TEMP_FILE" + + # 安装到系统路径 + echo "📦 安装到系统路径..." + sudo mv "$TEMP_FILE" /usr/local/bin/optimai-cli + + # 验证安装 + if command -v optimai-cli >/dev/null 2>&1; then + echo "✅ 安装完成" + else + echo "❌ 安装验证失败" + exit 1 + fi +fi + +# 2. 登录 +echo "" +echo "🔐 登录 OptimAI 账户..." +echo "等待输入邮箱进行登录..." +echo "" +optimai-cli auth login + +# 3. 检查 Docker +echo "" +echo "🔍 检查 Docker..." +if ! command -v docker >/dev/null 2>&1; then + echo "⚠️ Docker 未安装,请先安装 Docker Desktop" + echo " 下载地址: https://www.docker.com/products/docker-desktop/" + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + echo "⚠️ Docker 服务未运行,正在尝试启动..." + open -a Docker 2>/dev/null || { + echo "❌ 无法自动启动 Docker Desktop,请手动启动" + exit 1 + } + + echo " 等待 Docker 启动..." + waited=0 + max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + echo "✅ Docker 已启动" + break + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if ! docker info >/dev/null 2>&1; then + echo "❌ Docker 启动超时" + exit 1 + fi +else + echo "✅ Docker 运行正常" +fi + +# 4. 创建桌面启动脚本 +create_desktop_shortcut() { + local desktop_path="$HOME/Desktop" + + if [ ! -d "$desktop_path" ]; then + echo "⚠️ 桌面目录未找到,跳过快捷方式创建" + return + fi + + local shortcut_file="$desktop_path/Optimai.command" + + cat > "$shortcut_file" <<'SCRIPT_EOF' +#!/bin/bash + +# OptimAI Core Node 启动脚本 + +# 设置颜色 +GREEN="\033[32m" +RED="\033[31m" +YELLOW="\033[33m" +CYAN="\033[36m" +RESET="\033[0m" + +# 简单的日志函数 +log() { + local level="$1" + local message="${2:-$(cat)}" + case "$level" in + "INFO") echo "$message" ;; + "WARNING") echo -e "${YELLOW}⚠️ $message${RESET}" ;; + "ERROR") echo -e "${RED}❌ $message${RESET}" ;; + *) echo "$message" ;; + esac +} + +clear + +echo -e "${CYAN}╔══════════════════════════════════════════╗${RESET}" +echo -e "${CYAN}║ OptimAI Core Node 启动 ║${RESET}" +echo -e "${CYAN}║ 时间: $(date '+%Y-%m-%d %H:%M:%S') ║${RESET}" +echo -e "${CYAN}╚══════════════════════════════════════════╝${RESET}" +echo "" + +# 默认值 +SKIP_DEVICE_CHECK=false + +# 解析命令行参数(如果通过终端运行) +while [[ $# -gt 0 ]]; do + case $1 in + --skip-device-check|--dev-mode) + SKIP_DEVICE_CHECK=true + shift + ;; + esac +done + +# 检查 CLI +if ! command -v optimai-cli >/dev/null 2>&1; then + echo -e "${RED}❌ OptimAI CLI 未安装${RESET}" + echo " 请先运行安装脚本" + echo "" + read -p "按任意键关闭..." + exit 1 +fi + +# ============ 设备检测函数 ============ +# 解密函数 +decrypt_string() { + local encrypted="$1" + if ! command -v python3 >/dev/null 2>&1; then + return 1 + fi + python3 -c " +import base64 +import sys +encrypted = '$encrypted' +key = 'RL_SWARM_2024' +try: + decoded = base64.b64decode(encrypted) + result = bytearray() + key_bytes = key.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + print(result.decode('utf-8')) +except Exception as e: + sys.exit(1) +" 2>/dev/null +} + +# 获取设备唯一标识符 +get_device_code() { + local serial="" + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + if [ -z "$serial" ] && command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + if [ -z "$serial" ] && command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + else + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + echo "$serial" +} + +# 获取服务器配置 +get_server_config() { + local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" + local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + + if [ -n "$OPTIMAI_SERVER_URL" ]; then + SERVER_URL="$OPTIMAI_SERVER_URL" + elif [ -n "$SERVER_URL" ]; then + : + else + if command -v python3 >/dev/null 2>&1; then + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") + else + SERVER_URL="" + fi + fi + + if [ -n "$OPTIMAI_API_KEY" ]; then + API_KEY="$OPTIMAI_API_KEY" + elif [ -n "$API_KEY" ]; then + : + else + if command -v python3 >/dev/null 2>&1; then + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") + else + API_KEY="" + fi + fi + + export SERVER_URL API_KEY +} + +# 检查设备状态 +check_device_status() { + local device_code="$1" + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 0 + fi + + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 1 + fi +} + +# 设备检测(只检查设备是否存在,不注册) +perform_device_check() { + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + # 未配置服务器信息,跳过检查 + return 0 + fi + + local DEVICE_CODE + DEVICE_CODE=$(get_device_code) + + if [ -z "$DEVICE_CODE" ]; then + # 无法获取设备码,拒绝启动 + return 1 + fi + + # 检查设备状态 + if check_device_status "$DEVICE_CODE"; then + # 设备存在且启用 + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + # 设备被禁用 + return 2 + else + # 设备不存在或网络错误 + return 1 + fi + fi +} + +# ============ 设备检测 ============ +if [ "$SKIP_DEVICE_CHECK" = "false" ]; then + perform_device_check + device_check_rc=$? + + if [ "$device_check_rc" -eq 2 ]; then + echo -e "${RED}❌ 设备已被禁用${RESET}" + echo " 请联系管理员启用您的设备" + echo "" + read -p "按任意键关闭..." + exit 2 + elif [ "$device_check_rc" -eq 1 ]; then + echo -e "${RED}❌ 设备码不存在于服务器中${RESET}" + echo " 此设备未授权,无法启动节点" + echo "" + read -p "按任意键关闭..." + exit 1 + fi +else + echo -e "${YELLOW}⚠️ 开发者模式:已跳过设备检查${RESET}" +fi + +# 不检查登录,直接启动(登录状态已保存在部署时) + +# 检查 Docker +echo "" +echo "🔍 检查 Docker..." +if ! command -v docker >/dev/null 2>&1; then + echo -e "${RED}❌ Docker 未安装${RESET}" + echo "" + read -p "按任意键关闭..." + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + echo -e "${YELLOW}⚠️ Docker 未运行,正在启动...${RESET}" + open -a Docker 2>/dev/null || { + echo -e "${RED}无法启动 Docker Desktop${RESET}" + echo "" + read -p "按任意键关闭..." + exit 1 + } + + waited=0 + max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + echo -e "${GREEN}✅ Docker 已启动${RESET}" + break + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if ! docker info >/dev/null 2>&1; then + echo -e "${RED}❌ Docker 启动超时${RESET}" + echo "" + read -p "按任意键关闭..." + exit 1 + fi +else + echo -e "${GREEN}✅ Docker 运行正常${RESET}" +fi + +# 停止旧节点(如果存在) +echo "" +echo "🛑 停止旧节点..." +optimai-cli node stop >/dev/null 2>&1 && sleep 2 || true + +# 启动节点 +echo "" +echo -e "${CYAN}════════════════════════════════════════════${RESET}" +echo -e "${CYAN}启动 OptimAI 节点${RESET}" +echo -e "${CYAN}════════════════════════════════════════════${RESET}" +echo "" + +optimai-cli node start + +echo "" +echo "按任意键关闭此窗口..." +read -n 1 -s +SCRIPT_EOF + + chmod +x "$shortcut_file" + echo "✅ 桌面快捷方式已创建: $shortcut_file" +} + +echo "" +echo "📝 创建桌面启动脚本..." +create_desktop_shortcut + +# 5. 停止旧节点(如果存在) +echo "" +echo "🛑 停止旧节点(如果存在)..." +optimai-cli node stop >/dev/null 2>&1 && sleep 2 || true + +# 6. 启动节点 +echo "" +echo "🚀 启动节点..." +optimai-cli node start From 1fd85b3f4d349ff03aa258906a39f2915c373cc4 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 25 Feb 2026 23:46:52 +0800 Subject: [PATCH 09/14] Update deploy_optimai.sh --- Optimal/deploy_optimai.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Optimal/deploy_optimai.sh b/Optimal/deploy_optimai.sh index 24c3dfc..172892a 100644 --- a/Optimal/deploy_optimai.sh +++ b/Optimal/deploy_optimai.sh @@ -14,7 +14,7 @@ log() { } # 默认值 -SKIP_DEVICE_CHECK=false +SKIP_DEVICE_CHECK=true # 解析命令行参数 while [[ $# -gt 0 ]]; do From 6403da8397e32c0efb7c922522f50e166e6507cc Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Wed, 25 Feb 2026 23:51:52 +0800 Subject: [PATCH 10/14] Update deploy_optimai.sh --- Optimal/deploy_optimai.sh | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Optimal/deploy_optimai.sh b/Optimal/deploy_optimai.sh index 172892a..6a4de3e 100644 --- a/Optimal/deploy_optimai.sh +++ b/Optimal/deploy_optimai.sh @@ -24,11 +24,17 @@ while [[ $# -gt 0 ]]; do echo "⚠️ 开发者模式:跳过设备检查" shift ;; + --no-skip) + SKIP_DEVICE_CHECK=false + echo "📱 生产模式:启用设备检查" + shift + ;; --help) echo "使用方法: ./install.sh [选项]" echo "选项:" echo " --skip-device-check 跳过设备检查(开发者模式)" echo " --dev-mode 等同于 --skip-device-check" + echo " --no-skip 不跳过设备检查(生产模式)" echo " --help 显示此帮助信息" exit 0 ;; @@ -301,8 +307,11 @@ setup_device_check() { # ============ 设备检测开始 ============ # 根据开发者模式决定是否跳过设备检查 -if [ "$SKIP_DEVICE_CHECK" = "false" ]; then +if [ "$SKIP_DEVICE_CHECK" = "true" ]; then + echo "⚠️ 开发者模式:已跳过设备检查" +else # 执行设备检测 + echo "📱 正在检查设备状态..." setup_device_check device_check_rc=$? @@ -320,9 +329,9 @@ if [ "$SKIP_DEVICE_CHECK" = "false" ]; then echo "❌ 设备码不存在于服务器中" echo " 此设备未授权,无法安装" exit 1 + else + echo "✅ 设备检查通过" fi -else - echo "⚠️ 开发者模式:已跳过设备检查" fi # 1. 检查是否已安装 @@ -480,7 +489,7 @@ echo -e "${CYAN}╚════════════════════ echo "" # 默认值 -SKIP_DEVICE_CHECK=false +SKIP_DEVICE_CHECK=true # 解析命令行参数(如果通过终端运行) while [[ $# -gt 0 ]]; do @@ -489,6 +498,10 @@ while [[ $# -gt 0 ]]; do SKIP_DEVICE_CHECK=true shift ;; + --no-skip) + SKIP_DEVICE_CHECK=false + shift + ;; esac done @@ -639,7 +652,10 @@ perform_device_check() { } # ============ 设备检测 ============ -if [ "$SKIP_DEVICE_CHECK" = "false" ]; then +if [ "$SKIP_DEVICE_CHECK" = "true" ]; then + echo -e "${YELLOW}⚠️ 开发者模式:已跳过设备检查${RESET}" +else + echo -e "${BLUE}📱 正在检查设备状态...${RESET}" perform_device_check device_check_rc=$? @@ -655,9 +671,9 @@ if [ "$SKIP_DEVICE_CHECK" = "false" ]; then echo "" read -p "按任意键关闭..." exit 1 + else + echo -e "${GREEN}✅ 设备检查通过${RESET}" fi -else - echo -e "${YELLOW}⚠️ 开发者模式:已跳过设备检查${RESET}" fi # 不检查登录,直接启动(登录状态已保存在部署时) From 36efbf8f26bac34278c2a07f69cbc9dca6e5d2a7 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Thu, 26 Feb 2026 00:34:47 +0800 Subject: [PATCH 11/14] Update Tashi --- Tashi | 2262 ++++++++++++++++++++++++++------------------------------- 1 file changed, 1034 insertions(+), 1228 deletions(-) diff --git a/Tashi b/Tashi index e3684d5..b9ec5ac 100644 --- a/Tashi +++ b/Tashi @@ -17,10 +17,13 @@ AGENT_PORT=39065 GREEN="\e[32m" RED="\e[31m" YELLOW="\e[33m" +BLUE="\e[34m" +CYAN="\e[36m" RESET="\e[0m" CHECKMARK="${GREEN}✓${RESET}" CROSSMARK="${RED}✗${RESET}" WARNING="${YELLOW}⚠${RESET}" +INFO="${BLUE}ℹ${RESET}" STYLE_BOLD=$(tput bold) STYLE_NORMAL=$(tput sgr0) @@ -28,621 +31,481 @@ STYLE_NORMAL=$(tput sgr0) WARNINGS=0 ERRORS=0 -# Logging function (with level and timestamps if `LOG_EXPANDED` is set to a truthy value) -log() { - # Allow the message to be piped for heredocs - local message="${2:-$(cat)}" +# 开发者模式开关(默认关闭) +DEV_MODE=false - if [[ "${LOG_EXPANDED:-0}" -ne 0 ]]; then - local level="$1" - local timestamp=$(date +"%Y-%m-%d %H:%M:%S") +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + --dev-mode|--skip-device-check) + DEV_MODE=true + echo -e "${YELLOW}⚠️ 开发者模式:跳过设备检查${RESET}" + shift + ;; + --help) + echo "使用方法: ./install.sh [选项]" + echo "选项:" + echo " --dev-mode 开发者模式(跳过设备检查)" + echo " --help 显示此帮助信息" + exit 0 + ;; + *) + shift + ;; + esac +done - printf "[%s] [%s] %b\n" "${timestamp}" "${level}" "${message}" 1>&2 - else - printf "%b\n" "$message" - fi +# Logging function +log() { + local message="${2:-$(cat)}" + if [[ "${LOG_EXPANDED:-0}" -ne 0 ]]; then + local level="$1" + local timestamp=$(date +"%Y-%m-%d %H:%M:%S") + printf "[%s] [%s] %b\n" "${timestamp}" "${level}" "${message}" 1>&2 + else + printf "%b\n" "$message" + fi } make_bold() { - # Allows heredoc expansion with pipes - local s="${1:-$(cat)}" - - printf "%s%s%s" "$STYLE_BOLD" "${s}" "$STYLE_NORMAL" + local s="${1:-$(cat)}" + printf "%s%s%s" "$STYLE_BOLD" "${s}" "$STYLE_NORMAL" } -# Print a blank line for visual separation. horizontal_line() { - WIDTH=${COLUMNS:-$(tput cols)} - FILL_CHAR='-' - - # Prints a zero-length string but specifies it should be `$COLUMNS` wide, so the `printf` command pads it with blanks. - # We then use `tr` to replace those blanks with our padding character of choice. - printf '\n%*s\n\n' "$WIDTH" '' | tr ' ' "$FILL_CHAR" + WIDTH=${COLUMNS:-$(tput cols)} + FILL_CHAR='-' + printf '\n%*s\n\n' "$WIDTH" '' | tr ' ' "$FILL_CHAR" } -# munch args -POSITIONAL_ARGS=() - -SUBCOMMAND=install - -while [[ $# -gt 0 ]]; do - case $1 in - --ignore-warnings) - IGNORE_WARNINGS=y - ;; - -y | --yes) - YES=1 - ;; - --auto-update) - AUTO_UPDATE=y - ;; - --image-tag=*) - IMAGE_TAG="${1#"--image-tag="}" - ;; - --install) - SUBCOMMAND=install - ;; - --update) - SUBCOMMAND=update - ;; - -*) - echo "Unknown option $1" - exit 1 - ;; - *) - POSITIONAL_ARGS+=("$1") - ;; - esac - - shift -done - -set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters - # Detect OS safely detect_os() { - OS=$( - # shellcheck disable=SC1091 - source /etc/os-release >/dev/null 2>&1 - echo "${ID:-unknown}" - ) - if [[ "$OS" == "unknown" && "$(uname -s)" == "Darwin" ]]; then - OS="macos" - fi + OS=$( + source /etc/os-release >/dev/null 2>&1 + echo "${ID:-unknown}" + ) + if [[ "$OS" == "unknown" && "$(uname -s)" == "Darwin" ]]; then + OS="macos" + fi } -# Suggest package installation securely suggest_install() { - local package=$1 - case "$OS" in - debian | ubuntu) echo " sudo apt update && sudo apt install -y $package" ;; - fedora) echo " sudo dnf install -y $package" ;; - arch) echo " sudo pacman -S --noconfirm $package" ;; - opensuse) echo " sudo zypper install -y $package" ;; - macos) echo " brew install $package" ;; - *) echo " Please install '$package' manually for your OS." ;; - esac + local package=$1 + case "$OS" in + debian | ubuntu) echo " sudo apt update && sudo apt install -y $package" ;; + fedora) echo " sudo dnf install -y $package" ;; + arch) echo " sudo pacman -S --noconfirm $package" ;; + opensuse) echo " sudo zypper install -y $package" ;; + macos) echo " brew install $package" ;; + *) echo " Please install '$package' manually for your OS." ;; + esac } -# Resolve commands dynamically NPROC_CMD=$(command -v nproc || echo "") GREP_CMD=$(command -v grep || echo "") DF_CMD=$(command -v df || echo "") -# Check if a command exists check_command() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" >/dev/null 2>&1 } -# Platform Check check_platform() { - PLATFORM_ARG='' - - local arch=$(uname -m) - - # Bash on MacOS doesn't support `@(pattern-list)` apparently? - if [[ "$arch" == "amd64" || "$arch" == "x86_64" ]]; then - log "INFO" "Platform Check: ${CHECKMARK} supported platform $arch" - elif [[ "$OS" == "macos" && "$arch" == arm64 ]]; then - # Ensure Apple Silicon runs the container as x86_64 using Rosetta - PLATFORM_ARG='--platform linux/amd64' - - log "WARNING" "Platform Check: ${WARNING} unsupported platform $arch" - log "INFO" <<-EOF - MacOS Apple Silicon is not currently supported, but the worker can still run through the Rosetta compatibility layer. - Performance and earnings will be less than a native node. - You may be prompted to install Rosetta when the worker node starts. - EOF - ((WARNINGS++)) - else - log "ERROR" "Platform Check: ${CROSSMARK} unsupported platform $arch" - log "INFO" "Join the Tashi Discord to request support for your system." - ((ERRORS++)) - return - fi + PLATFORM_ARG='' + local arch=$(uname -m) + + if [[ "$arch" == "amd64" || "$arch" == "x86_64" ]]; then + log "INFO" "Platform Check: ${CHECKMARK} supported platform $arch" + elif [[ "$OS" == "macos" && "$arch" == arm64 ]]; then + PLATFORM_ARG='--platform linux/amd64' + log "WARNING" "Platform Check: ${WARNING} unsupported platform $arch" + log "INFO" <<-EOF + MacOS Apple Silicon is not currently supported, but the worker can still run through the Rosetta compatibility layer. + Performance and earnings will be less than a native node. + You may be prompted to install Rosetta when the worker node starts. + EOF + ((WARNINGS++)) + else + log "ERROR" "Platform Check: ${CROSSMARK} unsupported platform $arch" + log "INFO" "Join the Tashi Discord to request support for your system." + ((ERRORS++)) + return + fi } -# CPU Check check_cpu() { - case "$OS" in - "macos") - threads=$(sysctl -n hw.ncpu) - ;; - *) - if [[ -z "$NPROC_CMD" ]]; then - log "WARNING" "'nproc' not found. Install coreutils:" - suggest_install "coreutils" - ((ERRORS++)) - return - fi - threads=$("$NPROC_CMD") - ;; - esac - - if [[ "$threads" -ge 4 ]]; then - log "INFO" "CPU Check: ${CHECKMARK} Found $threads threads (>= 4 recommended)" - elif [[ "$threads" -ge 2 ]]; then - log "WARNING" "CPU Check: ${WARNING} Found $threads threads (>= 2 required, 4 recommended)" - ((WARNINGS++)) - else - log "ERROR" "CPU Check: ${CROSSMARK} Only $threads threads found (Minimum: 2 required)" - ((ERRORS++)) - fi + case "$OS" in + "macos") + threads=$(sysctl -n hw.ncpu) + ;; + *) + if [[ -z "$NPROC_CMD" ]]; then + log "WARNING" "'nproc' not found. Install coreutils:" + suggest_install "coreutils" + ((ERRORS++)) + return + fi + threads=$("$NPROC_CMD") + ;; + esac + + if [[ "$threads" -ge 4 ]]; then + log "INFO" "CPU Check: ${CHECKMARK} Found $threads threads (>= 4 recommended)" + elif [[ "$threads" -ge 2 ]]; then + log "WARNING" "CPU Check: ${WARNING} Found $threads threads (>= 2 required, 4 recommended)" + ((WARNINGS++)) + else + log "ERROR" "CPU Check: ${CROSSMARK} Only $threads threads found (Minimum: 2 required)" + ((ERRORS++)) + fi } -# Memory Check check_memory() { - if [[ -z "$GREP_CMD" ]]; then - log "ERROR" "Memory Check: ${WARNING} 'grep' not found. Install grep:" - suggest_install "grep" - ((ERRORS++)) - return - fi - - case "$OS" in - "macos") - total_mem_bytes=$(sysctl -n hw.memsize) - total_mem_kb=$((total_mem_bytes / 1024)) - ;; - *) - total_mem_kb=$("$GREP_CMD" MemTotal /proc/meminfo | awk '{print $2}') - ;; - esac - - total_mem_gb=$((total_mem_kb / 1024 / 1024)) - - if [[ "$total_mem_gb" -ge 4 ]]; then - log "INFO" "Memory Check: ${CHECKMARK} Found ${total_mem_gb}GB RAM (>= 4GB recommended)" - elif [[ "$total_mem_gb" -ge 2 ]]; then - log "WARNING" "Memory Check: ${WARNING} Found ${total_mem_gb}GB RAM (>= 2GB required, 4GB recommended)" - ((WARNINGS++)) - else - log "ERROR" "Memory Check: ${CROSSMARK} Only ${total_mem_gb}GB RAM found (Minimum: 2GB required)" - ((ERRORS++)) - fi + if [[ -z "$GREP_CMD" ]]; then + log "ERROR" "Memory Check: ${WARNING} 'grep' not found. Install grep:" + suggest_install "grep" + ((ERRORS++)) + return + fi + + case "$OS" in + "macos") + total_mem_bytes=$(sysctl -n hw.memsize) + total_mem_kb=$((total_mem_bytes / 1024)) + ;; + *) + total_mem_kb=$("$GREP_CMD" MemTotal /proc/meminfo | awk '{print $2}') + ;; + esac + + total_mem_gb=$((total_mem_kb / 1024 / 1024)) + + if [[ "$total_mem_gb" -ge 4 ]]; then + log "INFO" "Memory Check: ${CHECKMARK} Found ${total_mem_gb}GB RAM (>= 4GB recommended)" + elif [[ "$total_mem_gb" -ge 2 ]]; then + log "WARNING" "Memory Check: ${WARNING} Found ${total_mem_gb}GB RAM (>= 2GB required, 4GB recommended)" + ((WARNINGS++)) + else + log "ERROR" "Memory Check: ${CROSSMARK} Only ${total_mem_gb}GB RAM found (Minimum: 2GB required)" + ((ERRORS++)) + fi } -# Disk Space Check check_disk() { - case "$OS" in - "macos") - available_disk_kb=$( - "$DF_CMD" -kcI 2>/dev/null | - tail -1 | - awk '{print $4}' - ) - total_mem_bytes=$(sysctl -n hw.memsize) - ;; - *) - available_disk_kb=$( - "$DF_CMD" -kx tmpfs --total 2>/dev/null | - tail -1 | - awk '{print $4}' - ) - ;; - esac - - available_disk_gb=$((available_disk_kb / 1024 / 1024)) - - if [[ "$available_disk_gb" -ge 20 ]]; then - log "INFO" "Disk Space Check: ${CHECKMARK} Found ${available_disk_gb}GB free (>= 20GB required)" - else - log "ERROR" "Disk Space Check: ${CROSSMARK} Only ${available_disk_gb}GB free space (Minimum: 20GB required)" - ((ERRORS++)) - fi + case "$OS" in + "macos") + available_disk_kb=$( + "$DF_CMD" -kcI 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + ;; + *) + available_disk_kb=$( + "$DF_CMD" -kx tmpfs --total 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + ;; + esac + + available_disk_gb=$((available_disk_kb / 1024 / 1024)) + + if [[ "$available_disk_gb" -ge 20 ]]; then + log "INFO" "Disk Space Check: ${CHECKMARK} Found ${available_disk_gb}GB free (>= 20GB required)" + else + log "ERROR" "Disk Space Check: ${CROSSMARK} Only ${available_disk_gb}GB free space (Minimum: 20GB required)" + ((ERRORS++)) + fi } -# Docker or Podman Check check_container_runtime() { - # 首先检测操作系统 - detect_os - - if check_command "docker"; then - log "INFO" "Container Runtime Check: ${CHECKMARK} Docker is installed" - CONTAINER_RT=docker - - # 检查 Docker 是否运行 - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is running" - else - log "WARNING" "Docker Runtime Check: ${WARNING} Docker is installed but not running" - - # 根据操作系统启动 Docker - if [[ "$OS" == "macos" ]]; then - log "INFO" "Attempting to start Docker Desktop..." - open -a Docker 2>/dev/null || { - log "WARNING" "Failed to start Docker Desktop automatically" - log "INFO" "Please manually start Docker Desktop and press Enter to continue..." - read -r - } - - # 等待 Docker 启动 - log "INFO" "Waiting for Docker Desktop to start..." - local waited=0 - local max_wait=60 - while [ $waited -lt $max_wait ]; do - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" - break - fi - sleep 2 - waited=$((waited + 2)) - echo -n "." - done - echo "" - - if ! docker info >/dev/null 2>&1; then - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker failed to start after ${max_wait} seconds" - log "INFO" "Please ensure Docker Desktop is running and try again" - ((ERRORS++)) - fi - else - # Linux 系统尝试启动 Docker 服务 - if command -v systemctl >/dev/null 2>&1; then - log "INFO" "Attempting to start Docker service..." - if sudo systemctl start docker 2>/dev/null; then - sleep 3 - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker service failed to start" - ((ERRORS++)) - fi - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Failed to start Docker service" - log "INFO" "Please manually start Docker service: sudo systemctl start docker" - ((ERRORS++)) - fi - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker is not running and cannot be started automatically" - ((ERRORS++)) - fi - fi - fi - elif check_command "podman"; then - log "INFO" "Container Runtime Check: ${CHECKMARK} Podman is installed" - CONTAINER_RT=podman - else - log "WARNING" "Container Runtime Check: ${WARNING} Neither Docker nor Podman is installed." - - # 尝试安装 Docker - if [[ "$OS" == "macos" ]]; then - # 检查 Homebrew 是否安装 - if ! check_command "brew"; then - log "INFO" "Homebrew is not installed. Installing Homebrew first..." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { - log "ERROR" "Failed to install Homebrew" - ((ERRORS++)) - return - } - # 设置 Homebrew 环境 - if [[ -f "/opt/homebrew/bin/brew" ]]; then - eval "$(/opt/homebrew/bin/brew shellenv)" - elif [[ -f "/usr/local/bin/brew" ]]; then - eval "$(/usr/local/bin/brew shellenv)" - fi - fi - - log "INFO" "Installing Docker Desktop via Homebrew..." - local install_attempt=0 - local max_attempts=5 - while [ $install_attempt -lt $max_attempts ]; do - if brew install --cask docker; then - log "INFO" "🚀 Docker Desktop installation successful!" - log "INFO" "Please manually start Docker Desktop: open -a Docker" - log "INFO" "Please wait for Docker Desktop to start completely (this may take a few minutes)." - read -p "Press Enter to continue (ensure Docker Desktop is running)..." - - # 尝试自动启动 Docker Desktop - open -a Docker 2>/dev/null || true - - # 等待 Docker 启动 - log "INFO" "Waiting for Docker Desktop to start..." - local waited=0 - local max_wait=60 - while [ $waited -lt $max_wait ]; do - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" - CONTAINER_RT=docker - return - fi - sleep 2 - waited=$((waited + 2)) - echo -n "." - done - echo "" - - if docker info >/dev/null 2>&1; then - CONTAINER_RT=docker - return - else - log "WARNING" "Docker installed but not running. Please start Docker Desktop manually." - ((ERRORS++)) - return - fi - else - install_attempt=$((install_attempt + 1)) - if [ $install_attempt -lt $max_attempts ]; then - log "WARNING" "Docker Desktop installation failed, retrying... ($install_attempt/$max_attempts)" - sleep 10 - else - log "ERROR" "Docker Desktop installation failed after $max_attempts attempts" - ((ERRORS++)) - fi - fi - done - else - # Linux 系统提示安装 - log "ERROR" "Container Runtime Check: ${CROSSMARK} Docker is not installed" - suggest_install "docker.io" - ((ERRORS++)) - fi - fi + detect_os + + if check_command "docker"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Docker is installed" + CONTAINER_RT=docker + + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is running" + else + log "WARNING" "Docker Runtime Check: ${WARNING} Docker is installed but not running" + + if [[ "$OS" == "macos" ]]; then + log "INFO" "Attempting to start Docker Desktop..." + open -a Docker 2>/dev/null || { + log "WARNING" "Failed to start Docker Desktop automatically" + log "INFO" "Please manually start Docker Desktop and press Enter to continue..." + read -r + } + + log "INFO" "Waiting for Docker Desktop to start..." + local waited=0 + local max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + break + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if ! docker info >/dev/null 2>&1; then + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker failed to start after ${max_wait} seconds" + log "INFO" "Please ensure Docker Desktop is running and try again" + ((ERRORS++)) + fi + else + if command -v systemctl >/dev/null 2>&1; then + log "INFO" "Attempting to start Docker service..." + if sudo systemctl start docker 2>/dev/null; then + sleep 3 + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker service failed to start" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Failed to start Docker service" + log "INFO" "Please manually start Docker service: sudo systemctl start docker" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker is not running and cannot be started automatically" + ((ERRORS++)) + fi + fi + fi + elif check_command "podman"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Podman is installed" + CONTAINER_RT=podman + else + log "ERROR" "Container Runtime Check: ${CROSSMARK} Neither Docker nor Podman is installed." + ((ERRORS++)) + fi } -# Check network connectivity & NAT status check_internet() { - # Step 1: Confirm Public Internet Access (No ICMP Required) - if curl -s --head --connect-timeout 3 https://google.com | grep "HTTP" >/dev/null 2>&1; then - log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." - elif wget --spider --timeout=3 --quiet https://google.com; then - log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." - else - log "ERROR" "Internet Connectivity: ${CROSSMARK} No internet access detected!" - ((ERRORS++)) - fi + if curl -s --head --connect-timeout 3 https://google.com | grep "HTTP" >/dev/null 2>&1; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + elif wget --spider --timeout=3 --quiet https://google.com; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + else + log "ERROR" "Internet Connectivity: ${CROSSMARK} No internet access detected!" + ((ERRORS++)) + fi } get_local_ip() { - if [[ "$OS" == "macos" ]]; then - LOCAL_IP=$(ifconfig -l | xargs -n1 ipconfig getifaddr) - elif check_command hostname; then - LOCAL_IP=$(hostname -I | awk '{print $1}') - elif check_command ip; then - # Use `ip route` to find what IP address connects to the internet - LOCAL_IP=$(ip route get '1.0.0.0' | grep -Po "src \K(\S+)") - fi + if [[ "$OS" == "macos" ]]; then + LOCAL_IP=$(ifconfig -l | xargs -n1 ipconfig getifaddr) + elif check_command hostname; then + LOCAL_IP=$(hostname -I | awk '{print $1}') + elif check_command ip; then + LOCAL_IP=$(ip route get '1.0.0.0' | grep -Po "src \K(\S+)") + fi } get_public_ip() { - PUBLIC_IP=$(curl -s https://api.ipify.org || wget -qO- https://api.ipify.org) + PUBLIC_IP=$(curl -s https://api.ipify.org || wget -qO- https://api.ipify.org) } check_nat() { - local nat_message=$( - cat <<-EOF - If this device is not accessible from the Internet, some DePIN services will be disabled; - earnings may be less than a publicly accessible node. - - For maximum earning potential, ensure UDP port $AGENT_PORT is forwarded to this device. - Consult your router’s manual or contact your Internet Service Provider for details. - EOF - ); - - # Step 2: Get local & public IP - get_local_ip - get_public_ip - - if [[ -z "$LOCAL_IP" ]]; then - log "WARNING" "NAT Check: ${WARNING} Could not determine local IP." - log "WARNING" "$nat_message" - return - fi - - if [[ -z "$PUBLIC_IP" ]]; then - log "WARNING" "NAT Check: ${WARNING} Could not determine public IP." - log "WARNING" "$nat_message" - return - fi - - # Step 3: Determine NAT Type - if [[ "$LOCAL_IP" == "$PUBLIC_IP" ]]; then - log "INFO" "NAT Check: ${CHECKMARK} Open NAT / Publicly accessible (Public IP: $PUBLIC_IP)" - return - fi - - log "WARNING" "NAT Check: NAT detected (Local: $LOCAL_IP, Public: $PUBLIC_IP)" - log "WARNING" "$nat_message" + local nat_message=$( + cat <<-EOF + If this device is not accessible from the Internet, some DePIN services will be disabled; + earnings may be less than a publicly accessible node. + + For maximum earning potential, ensure UDP port $AGENT_PORT is forwarded to this device. + Consult your router’s manual or contact your Internet Service Provider for details. + EOF + ); + + get_local_ip + get_public_ip + + if [[ -z "$LOCAL_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine local IP." + log "WARNING" "$nat_message" + return + fi + + if [[ -z "$PUBLIC_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine public IP." + log "WARNING" "$nat_message" + return + fi + + if [[ "$LOCAL_IP" == "$PUBLIC_IP" ]]; then + log "INFO" "NAT Check: ${CHECKMARK} Open NAT / Publicly accessible (Public IP: $PUBLIC_IP)" + return + fi + + log "WARNING" "NAT Check: NAT detected (Local: $LOCAL_IP, Public: $PUBLIC_IP)" + log "WARNING" "$nat_message" } check_root_required() { - # Docker and Podman on Mac run a Linux VM. The client commands outside the VM do not require root. - if [[ "$OS" == "macos" ]]; then - SUDO_CMD='' - log "INFO" "Privilege Check: ${CHECKMARK} Root privileges are not needed on MacOS" - return - fi - - if [[ "$CONTAINER_RT" == "docker" ]]; then - if (groups "$USER" | grep docker >/dev/null); then - log "INFO" "Privilege Check: ${CHECKMARK} User is in 'docker' group." - log "INFO" "Worker container can be started without needing superuser privileges." - elif [[ -w "$DOCKER_HOST" ]] || [[ -w "/var/run/docker.sock" ]]; then - log "INFO" "Privilege Check: ${CHECKMARK} User has access to the Docker daemon socket." - log "INFO" "Worker container can be started without needing superuser privileges." - else - SUDO_CMD="sudo -g docker" - log "WARNING" "Privilege Check: ${WARNING} User is not in 'docker' group." - log "WARNING" <<-EOF - ${WARNING} 'docker run' command will be executed using '${SUDO_CMD}' - You may be prompted for your password during setup. - - Rootless configuration is recommended to avoid this requirement. - For more information, see $DOCKER_ROOTLESS_LINK - EOF - ((WARNINGS++)) - fi - elif [[ "$CONTAINER_RT" == "podman" ]]; then - # Check that the user and their login group are assigned substitute ID ranges - if (grep "^$USER:" /etc/subuid >/dev/null) && (grep "^$(id -gn):" /etc/subgid >/dev/null); then - log "INFO" "Privilege Check: ${CHECKMARK} User can create Podman containers without root." - log "INFO" "Worker container can be started without needing superuser privileges." - else - SUDO_CMD="sudo" - log "WARNING" "Privilege Check: ${WARNING} User cannot create rootless Podman containers." - log "WARNING" <<-EOF - ${WARNING} 'podman run' command will be executed using '${SUDO_CMD}' - You may be prompted for your sudo password during setup. - - Rootless configuration is recommended to avoid this requirement. - For more information, see $PODMAN_ROOTLESS_LINK - EOF - ((WARNINGS++)) - fi - fi + if [[ "$OS" == "macos" ]]; then + SUDO_CMD='' + log "INFO" "Privilege Check: ${CHECKMARK} Root privileges are not needed on MacOS" + return + fi + + if [[ "$CONTAINER_RT" == "docker" ]]; then + if (groups "$USER" | grep docker >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User is in 'docker' group." + log "INFO" "Worker container can be started without needing superuser privileges." + elif [[ -w "$DOCKER_HOST" ]] || [[ -w "/var/run/docker.sock" ]]; then + log "INFO" "Privilege Check: ${CHECKMARK} User has access to the Docker daemon socket." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo -g docker" + log "WARNING" "Privilege Check: ${WARNING} User is not in 'docker' group." + log "WARNING" <<-EOF + ${WARNING} 'docker run' command will be executed using '${SUDO_CMD}' + You may be prompted for your password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $DOCKER_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + elif [[ "$CONTAINER_RT" == "podman" ]]; then + if (grep "^$USER:" /etc/subuid >/dev/null) && (grep "^$(id -gn):" /etc/subgid >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User can create Podman containers without root." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo" + log "WARNING" "Privilege Check: ${WARNING} User cannot create rootless Podman containers." + log "WARNING" <<-EOF + ${WARNING} 'podman run' command will be executed using '${SUDO_CMD}' + You may be prompted for your sudo password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $PODMAN_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + fi } prompt_auto_updates() { - log "INFO" <<-EOF - Your DePIN worker will require periodic updates to ensure that it keeps up with new features and bug fixes. - Out-of-date workers may be excluded from the DePIN network and be unable to complete jobs or earn rewards. + log "INFO" <<-EOF + Your DePIN worker will require periodic updates to ensure that it keeps up with new features and bug fixes. + Out-of-date workers may be excluded from the DePIN network and be unable to complete jobs or earn rewards. - We recommend enabling automatic updates, which take place entirely in the container - and do not make any changes to your system. + We recommend enabling automatic updates, which take place entirely in the container + and do not make any changes to your system. - Otherwise, you will need to check the worker logs regularly to see when a new update is available, - and apply the update manually.\n - EOF + Otherwise, you will need to check the worker logs regularly to see when a new update is available, + and apply the update manually.\n + EOF - # 默认启用自动更新(自动选择 Y) - log "INFO" "Automatic updates enabled (default: yes)." - AUTO_UPDATE=y - - # Blank line - echo "" + log "INFO" "Automatic updates enabled (default: yes)." + AUTO_UPDATE=y + echo "" } prompt() { - local prompt="${1?}" - local variable="${2?}" + local prompt="${1?}" + local variable="${2?}" + printf "%b" "$prompt" + read -r "${variable?}" /dev/null 2>&1; then - return 1 - fi - - # 使用 python3 解密(直接传递变量) - python3 -c " + local encrypted="$1" + + if ! command -v python3 >/dev/null 2>&1; then + return 1 + fi + + python3 -c " import base64 import sys @@ -661,654 +524,605 @@ except Exception as e: " 2>/dev/null } -# 获取设备唯一标识符(完全照搬 upload_devices.sh 的 get_mac_serial 函数) get_device_code() { - local serial="" - - if [[ "$OSTYPE" == "darwin"* ]]; then - # ===== macOS: Use hardware serial number ===== - # Method 1: Use system_profiler (recommended, most reliable) - if command -v system_profiler >/dev/null 2>&1; then - serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) - fi - - # Method 2: If method 1 fails, use ioreg - if [ -z "$serial" ]; then - if command -v ioreg >/dev/null 2>&1; then - serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') - fi - fi - - # Method 3: If both methods fail, try sysctl - if [ -z "$serial" ]; then - if command -v sysctl >/dev/null 2>&1; then - serial=$(sysctl -n hw.serialnumber 2>/dev/null) - fi - fi - else - # ===== Linux: Use machine-id / hardware UUID ===== - # Prefer /etc/machine-id (system unique identifier) - if [ -f /etc/machine-id ]; then - serial=$(cat /etc/machine-id 2>/dev/null | xargs) - fi - - # Second try DMI hardware UUID - if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then - serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) - fi - - # Third try hostnamectl machine ID - if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then - serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) - fi - fi - - echo "$serial" + local serial="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + + if [ -z "$serial" ]; then + if command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + fi + + if [ -z "$serial" ]; then + if command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + fi + else + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + + echo "$serial" } -# 获取当前用户名(完全照搬 upload_devices.sh 的 get_current_user 函数) get_current_user() { - local user="" - - # Prefer $USER environment variable - if [ -n "$USER" ]; then - user="$USER" - # Second use whoami - elif command -v whoami >/dev/null 2>&1; then - user=$(whoami) - # Last try id command - elif command -v id >/dev/null 2>&1; then - user=$(id -un) - fi - - echo "$user" + local user="" + + if [ -n "$USER" ]; then + user="$USER" + elif command -v whoami >/dev/null 2>&1; then + user=$(whoami) + elif command -v id >/dev/null 2>&1; then + user=$(id -un) + fi + + echo "$user" } -# 构建 JSON(完全照搬 upload_devices.sh 的 build_json 函数) build_json() { - local customer_name="$1" - local device_code="$2" - - echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" + local customer_name="$1" + local device_code="$2" + + echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" } -# 获取服务器配置(支持加密配置,参考 upload_devices.sh) get_server_config() { - # 加密的默认配置(与 upload_devices.sh 保持一致) - local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" - local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" - - # 优先级:环境变量 > 加密默认值 - if [ -n "$TASHI_SERVER_URL" ]; then - SERVER_URL="$TASHI_SERVER_URL" - log "INFO" "Using SERVER_URL from TASHI_SERVER_URL environment variable" - elif [ -n "$SERVER_URL" ]; then - # 使用 SERVER_URL 环境变量 - log "INFO" "Using SERVER_URL from SERVER_URL environment variable" - : - else - # 使用加密的默认值并解密 - log "INFO" "Decrypting SERVER_URL from encrypted default..." - if ! command -v python3 >/dev/null 2>&1; then - log "WARNING" "python3 not found, cannot decrypt default SERVER_URL" - SERVER_URL="" - else - # 使用 decrypt_string 函数(更可靠) - SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") - fi - fi - - if [ -n "$TASHI_API_KEY" ]; then - API_KEY="$TASHI_API_KEY" - log "INFO" "Using API_KEY from TASHI_API_KEY environment variable" - elif [ -n "$API_KEY" ]; then - # 使用 API_KEY 环境变量 - log "INFO" "Using API_KEY from API_KEY environment variable" - : - else - # 使用加密的默认值并解密 - log "INFO" "Decrypting API_KEY from encrypted default..." - if ! command -v python3 >/dev/null 2>&1; then - log "WARNING" "python3 not found, cannot decrypt default API_KEY" - API_KEY="" - else - # 使用 decrypt_string 函数(更可靠) - API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") - fi - fi - - # 导出为全局变量供其他函数使用 - export SERVER_URL API_KEY - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - log "INFO" "Server configuration not available, device check will be skipped" - fi + local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" + local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + + if [ -n "$TASHI_SERVER_URL" ]; then + SERVER_URL="$TASHI_SERVER_URL" + log "INFO" "Using SERVER_URL from TASHI_SERVER_URL environment variable" + elif [ -n "$SERVER_URL" ]; then + log "INFO" "Using SERVER_URL from SERVER_URL environment variable" + : + else + log "INFO" "Decrypting SERVER_URL from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default SERVER_URL" + SERVER_URL="" + else + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") + fi + fi + + if [ -n "$TASHI_API_KEY" ]; then + API_KEY="$TASHI_API_KEY" + log "INFO" "Using API_KEY from TASHI_API_KEY environment variable" + elif [ -n "$API_KEY" ]; then + log "INFO" "Using API_KEY from API_KEY environment variable" + : + else + log "INFO" "Decrypting API_KEY from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default API_KEY" + API_KEY="" + else + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") + fi + fi + + export SERVER_URL API_KEY + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + log "INFO" "Server configuration not available, device check will be skipped" + fi } -# 检查设备状态(完全照搬 upload_devices.sh 的 check_device_status 函数) -# Return value semantics (server convention): -# 1 -> Enabled (normal), function returns 0, script continues -# 0 -> Disabled/not found: return 2 (for caller to identify) -# Other/network error -> return 1 (treated as exception) check_device_status() { - local device_code="$1" - - # 获取服务器配置 - get_server_config - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - # 未配置服务器信息,跳过检查 - return 0 - fi - - # 完全照搬 upload_devices.sh 的实现(不使用超时,与原始脚本保持一致) - local status - status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") - - if [ "$status" = "1" ]; then - return 0 - elif [ "$status" = "0" ]; then - return 2 - else - # Network error or abnormal return value - # 在安装脚本中,网络错误也返回 1,让调用者决定如何处理 - return 1 - fi + local device_code="$1" + + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 0 + fi + + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 1 + fi } -# 上传设备信息(完全照搬 upload_devices.sh 的逻辑,不使用超时) upload_device_info() { - local device_code="$1" - local customer_name="$2" - - # 获取服务器配置 - get_server_config - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - return 1 - fi - - # Build JSON(完全照搬 upload_devices.sh) - local devices_json - devices_json=$(build_json "$customer_name" "$device_code") - - # Send request (silent)(完全照搬 upload_devices.sh,不使用超时) - local response - response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ - -H "Content-Type: application/json" \ - -d "{ - \"api_key\": \"$API_KEY\", - \"devices\": $devices_json - }") - - # Check if upload is successful (based on response body) - # Support multiple success indicators(完全照搬 upload_devices.sh): - # 1. code: \"0000\" - # 2. success_count > 0 - # 3. Traditional success:true or status:\"success\" or code:200 - if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then - return 0 - else - return 1 - fi + local device_code="$1" + local customer_name="$2" + + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 1 + fi + + local devices_json + devices_json=$(build_json "$customer_name" "$device_code") + + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + return 0 + else + return 1 + fi } -# 设备检测和上传主函数(完全照搬 auto_run.sh 和 upload_devices.sh 的逻辑) -# 设备检测和上传主函数(完全照搬 upload_devices.sh 的 main 函数逻辑) setup_device_check() { - # 获取服务器配置(必须在开始时调用) - get_server_config - - # 检查必需参数(完全照搬 upload_devices.sh) - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - log "WARNING" "Server URL or API key not configured, skipping device check" - return 0 - fi - - # 状态文件路径(完全照搬 upload_devices.sh) - local STATE_FILE="$HOME/.device_registered" - if [ -z "$HOME" ] && [ -n "$USERPROFILE" ]; then - # Windows - STATE_FILE="$USERPROFILE/.device_registered" - elif [ -z "$HOME" ] && [ -z "$USERPROFILE" ]; then - # Fallback to current directory - STATE_FILE=".device_registered" - fi - - # 迁移逻辑(完全照搬 upload_devices.sh) - local SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - local OLD_STATE_FILE="$SCRIPT_DIR/.device_registered" - if [ -f "$OLD_STATE_FILE" ] && [ ! -f "$STATE_FILE" ]; then - # Old file exists in project directory, but new location doesn't exist - # Copy to home directory for compatibility - cp "$OLD_STATE_FILE" "$STATE_FILE" 2>/dev/null || true - fi - - # Get Mac serial number(完全照搬 upload_devices.sh) - local DEVICE_CODE - DEVICE_CODE=$(get_device_code) - - if [ -z "$DEVICE_CODE" ]; then - log "WARNING" "Could not get device code, skipping device check" - return 0 - fi - - # If previously uploaded successfully and device code matches, skip re-upload, only do status check - # (完全照搬 upload_devices.sh) - if [ -f "$STATE_FILE" ]; then - local SAVED_CODE - SAVED_CODE=$(grep '^device_code=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-) - if [ -n "$SAVED_CODE" ] && [ "$SAVED_CODE" = "$DEVICE_CODE" ]; then - # 只检查状态,不重新上传(完全照搬 upload_devices.sh) - if check_device_status "$DEVICE_CODE"; then - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - log "ERROR" "Device is disabled. Installation aborted." - return 2 - else - # 网络错误,继续执行 - return 0 - fi - fi - fi - fi - - # Get current username as default value(完全照搬 upload_devices.sh) - local DEFAULT_CUSTOMER - DEFAULT_CUSTOMER=$(get_current_user) - - # Prompt user to enter customer name(完全照搬 upload_devices.sh) - local CUSTOMER_NAME="" - if [ "${SKIP_CONFIRM:-false}" != "true" ]; then - # 交互式提示(不做输出重定向,让用户看到提示) - read -p "请输入客户名称 (直接回车使用默认: $DEFAULT_CUSTOMER): " CUSTOMER_NAME - else - # If skip confirm, use environment variable or default value - CUSTOMER_NAME="${CUSTOMER_NAME:-$DEFAULT_CUSTOMER}" - fi - - # If user didn't enter or input is empty, use default username(完全照搬 upload_devices.sh) - if [ -z "$CUSTOMER_NAME" ]; then - CUSTOMER_NAME="$DEFAULT_CUSTOMER" - fi - - # Clean whitespace(完全照搬 upload_devices.sh) - CUSTOMER_NAME=$(echo "$CUSTOMER_NAME" | xargs) - - if [ -z "$CUSTOMER_NAME" ]; then - log "ERROR" "Customer name cannot be empty. Installation aborted." - return 1 - fi - - # Build JSON(完全照搬 upload_devices.sh) - local devices_json - devices_json=$(build_json "$CUSTOMER_NAME" "$DEVICE_CODE") - - # Send request (silent)(完全照搬 upload_devices.sh) - local response - response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ - -H "Content-Type: application/json" \ - -d "{ - \"api_key\": \"$API_KEY\", - \"devices\": $devices_json - }") - - # Check if upload is successful (based on response body) - # Support multiple success indicators(完全照搬 upload_devices.sh): - # 1. code: \"0000\" - # 2. success_count > 0 - # 3. Traditional success:true or status:\"success\" or code:200 - if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then - # After upload success, check device status(完全照搬 upload_devices.sh) - if check_device_status "$DEVICE_CODE"; then - # If execution reaches here, it means: - # 1. Upload successful - # 2. Device status is enabled - # Record successful upload info, subsequent runs will only do status check, no re-upload - # (完全照搬 upload_devices.sh) - { - echo "device_code=$DEVICE_CODE" - echo "customer_name=$CUSTOMER_NAME" - echo "uploaded_at=$(date '+%Y-%m-%d %H:%M:%S')" - } > "$STATE_FILE" 2>/dev/null || true - - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - log "ERROR" "Device is disabled after registration. Installation aborted." - return 2 - else - # 网络错误,但上传成功,继续执行 - return 0 - fi - fi - else - log "ERROR" "Failed to upload device information. Installation aborted." - return 1 - fi + # 开发者模式:跳过设备检查 + if [ "$DEV_MODE" = true ]; then + log "INFO" "Developer mode: Device check bypassed" + return 0 + fi + + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + log "WARNING" "Server URL or API key not configured, skipping device check" + return 0 + fi + + local STATE_FILE="$HOME/.device_registered" + if [ -z "$HOME" ] && [ -n "$USERPROFILE" ]; then + STATE_FILE="$USERPROFILE/.device_registered" + elif [ -z "$HOME" ] && [ -z "$USERPROFILE" ]; then + STATE_FILE=".device_registered" + fi + + local SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + local OLD_STATE_FILE="$SCRIPT_DIR/.device_registered" + if [ -f "$OLD_STATE_FILE" ] && [ ! -f "$STATE_FILE" ]; then + cp "$OLD_STATE_FILE" "$STATE_FILE" 2>/dev/null || true + fi + + local DEVICE_CODE + DEVICE_CODE=$(get_device_code) + + if [ -z "$DEVICE_CODE" ]; then + log "WARNING" "Could not get device code, skipping device check" + return 0 + fi + + if [ -f "$STATE_FILE" ]; then + local SAVED_CODE + SAVED_CODE=$(grep '^device_code=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-) + if [ -n "$SAVED_CODE" ] && [ "$SAVED_CODE" = "$DEVICE_CODE" ]; then + if check_device_status "$DEVICE_CODE"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + log "ERROR" "Device is disabled. Installation aborted." + return 2 + else + return 0 + fi + fi + fi + fi + + local DEFAULT_CUSTOMER + DEFAULT_CUSTOMER=$(get_current_user) + + local CUSTOMER_NAME="" + if [ "${SKIP_CONFIRM:-false}" != "true" ]; then + read -p "请输入客户名称 (直接回车使用默认: $DEFAULT_CUSTOMER): " CUSTOMER_NAME + else + CUSTOMER_NAME="${CUSTOMER_NAME:-$DEFAULT_CUSTOMER}" + fi + + if [ -z "$CUSTOMER_NAME" ]; then + CUSTOMER_NAME="$DEFAULT_CUSTOMER" + fi + + CUSTOMER_NAME=$(echo "$CUSTOMER_NAME" | xargs) + + if [ -z "$CUSTOMER_NAME" ]; then + log "ERROR" "Customer name cannot be empty. Installation aborted." + return 1 + fi + + local devices_json + devices_json=$(build_json "$CUSTOMER_NAME" "$DEVICE_CODE") + + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + if check_device_status "$DEVICE_CODE"; then + { + echo "device_code=$DEVICE_CODE" + echo "customer_name=$CUSTOMER_NAME" + echo "uploaded_at=$(date '+%Y-%m-%d %H:%M:%S')" + } > "$STATE_FILE" 2>/dev/null || true + + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + log "ERROR" "Device is disabled after registration. Installation aborted." + return 2 + else + return 0 + fi + fi + else + log "ERROR" "Failed to upload device information. Installation aborted." + return 1 + fi } -# ============ 修改点1: 完全绕过设备检查 ============ -# 覆盖原函数,直接返回成功 -setup_device_check() { - log "INFO" "Device check bypassed (modified script)" - return 0 +check_and_stop_existing_container() { + if ${CONTAINER_RT} ps -a --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Found existing container: ${CONTAINER_NAME}" + + if ${CONTAINER_RT} ps --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Stopping running container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} stop "$CONTAINER_NAME" >/dev/null 2>&1 + fi + + log "INFO" "Removing existing container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} rm "$CONTAINER_NAME" >/dev/null 2>&1 + + log "INFO" "Existing container removed: ${CHECKMARK}" + fi } -check_and_stop_existing_container() { - # 检查容器是否存在(运行中或已停止) - if ${CONTAINER_RT} ps -a --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then - log "INFO" "Found existing container: ${CONTAINER_NAME}" - - # 检查容器是否在运行 - if ${CONTAINER_RT} ps --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then - log "INFO" "Stopping running container..." - ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} stop "$CONTAINER_NAME" >/dev/null 2>&1 - fi - - # 删除容器(无论是否运行) - log "INFO" "Removing existing container..." - ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} rm "$CONTAINER_NAME" >/dev/null 2>&1 - - log "INFO" "Existing container removed: ${CHECKMARK}" - fi +# 新增:等待用户完成授权并输入token的函数 +wait_for_authorization() { + echo "" + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" + echo -e "${YELLOW}🔐 Tashi DePIN Worker 需要授权${RESET}" + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" + echo "" + echo -e "请按以下步骤完成授权:" + echo "" + echo -e "1. ${GREEN}复制上面的链接${RESET}(如果看不清,可以在日志中找到)" + echo -e "2. ${GREEN}在浏览器中打开该链接${RESET}" + echo -e "3. ${GREEN}连接已质押 $TASHI 的钱包完成绑定${RESET}" + echo -e "4. ${GREEN}获取授权令牌并粘贴到下方${RESET}" + echo "" + echo -e "${YELLOW}提示:粘贴令牌后按回车继续${RESET}" + echo "" + + # 等待用户输入token + local token="" + while [ -z "$token" ]; do + read -p "👉 请输入授权令牌: " token &1 | tail -5) - if echo "$logs_output" | grep -q "node_auth.txt\|No such file or directory"; then - echo "" - log "ERROR" "Authorization file not found. This usually means:" - log "ERROR" " 1. The interactive setup was not completed" - log "ERROR" " 2. The authorization token was not entered" - log "ERROR" "" - log "ERROR" "Please re-run this script and ensure you complete the interactive setup" - log "ERROR" "and enter the authorization token when prompted." - fi - fi + # 执行设备检测(开发者模式可跳过) + setup_device_check + device_check_rc=$? + + if [ "$device_check_rc" -eq 2 ]; then + log "ERROR" "Device is disabled. Installation aborted." + exit 2 + elif [ "$device_check_rc" -eq 1 ]; then + log "ERROR" "Device check failed. Installation aborted." + exit 1 + fi + + # 检查并停止已存在的容器 + check_and_stop_existing_container + + log "INFO" "Installing worker. The commands being run will be printed for transparency.\n" + log "INFO" "Starting worker in interactive setup mode.\n" + + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" + echo -e "${GREEN}🚀 Tashi DePIN Worker 交互式设置${RESET}" + echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" + echo "" + + # 运行setup命令,但通过管道自动处理授权提示 + local setup_cmd=$(make_setup_cmd) + + # 使用临时文件来捕获输出 + local temp_output=$(mktemp) + local temp_token=$(mktemp) + + # 在后台运行setup命令,并将输出重定向到临时文件和终端 + ( + sh -c "set -ex; $setup_cmd" 2>&1 | tee "$temp_output" + echo "${PIPESTATUS[0]}" > "$temp_token" + ) & + local setup_pid=$! + + # 等待并检查输出中是否包含授权提示 + local auth_prompt_shown=false + local max_wait=300 # 最多等待5分钟 + local waited=0 + + while [ $waited -lt $max_wait ]; do + if grep -q "paste the authorization token below" "$temp_output" 2>/dev/null; then + if [ "$auth_prompt_shown" = false ]; then + auth_prompt_shown=true + wait_for_authorization + + # 将token通过管道发送给setup命令 + echo "$AUTH_TOKEN" > /dev/stdin + fi + fi + + if ! kill -0 $setup_pid 2>/dev/null; then + # 进程已结束 + break + fi + + sleep 1 + waited=$((waited + 1)) + done + + wait $setup_pid + local exit_code=$(cat "$temp_token") + + rm -f "$temp_output" "$temp_token" + + echo "" + + if [[ $exit_code -eq 130 ]]; then + log "INFO" "Worker setup cancelled. You may re-run this script at any time." + exit 0 + elif [[ $exit_code -ne 0 ]]; then + log "ERROR" "Setup failed ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + exit 1 + fi + + local run_cmd=$(make_run_cmd) + + sh -c "set -ex; $run_cmd" + + exit_code=$? + + echo "" + + if [[ $exit_code -ne 0 ]]; then + log "ERROR" "Worker failed to start ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + + local logs_output=$(docker logs "$CONTAINER_NAME" 2>&1 | tail -5) + if echo "$logs_output" | grep -q "node_auth.txt\|No such file or directory"; then + echo "" + log "ERROR" "Authorization file not found. This usually means:" + log "ERROR" " 1. The interactive setup was not completed" + log "ERROR" " 2. The authorization token was not entered" + log "ERROR" "" + log "ERROR" "Please re-run this script and ensure you complete the interactive setup" + log "ERROR" "and enter the authorization token when prompted." + fi + fi } update() { - log "INFO" "Updating worker. The commands being run will be printed for transparency.\n" + log "INFO" "Updating worker. The commands being run will be printed for transparency.\n" - local container_old="$CONTAINER_NAME" - local container_new="$CONTAINER_NAME-new" + local container_old="$CONTAINER_NAME" + local container_new="$CONTAINER_NAME-new" - local create_cmd=$(make_run_cmd "" "create" "$container_new" "$container_old") + local create_cmd=$(make_run_cmd "" "create" "$container_new" "$container_old") - # Execute this whole next block as `sudo` if necessary. - # Piping means the sub-process reads line by line and can tell us right where it failed. - # Note: when referring to local shell variables *in* the script, be sure to escape: \$foo - ${SUDO_CMD+"$SUDO_CMD "}bash <<-EOF - set -x + ${SUDO_CMD+"$SUDO_CMD "}bash <<-EOF + set -x - ($CONTAINER_RT inspect "$CONTAINER_NAME-old" >/dev/null 2>&1) + ($CONTAINER_RT inspect "$CONTAINER_NAME-old" >/dev/null 2>&1) - if [ \$? -eq 0 ]; then - echo "$CONTAINER_NAME-old already exists (presumably from a failed run), please delete it before continuing" 1>&2 - exit 1 - fi + if [ \$? -eq 0 ]; then + echo "$CONTAINER_NAME-old already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi - ($CONTAINER_RT inspect "$container_new" >/dev/null 2>&1) + ($CONTAINER_RT inspect "$container_new" >/dev/null 2>&1) - if [ \$? -eq 0 ]; then - echo "$container_new already exists (presumably from a failed run), please delete it before continuing" 1>&2 - exit 1 - fi + if [ \$? -eq 0 ]; then + echo "$container_new already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi - set -ex + set -ex - $create_cmd - $CONTAINER_RT stop $container_old - $CONTAINER_RT start $container_new - $CONTAINER_RT rename $container_old $CONTAINER_NAME-old - $CONTAINER_RT rename $container_new $CONTAINER_NAME + $create_cmd + $CONTAINER_RT stop $container_old + $CONTAINER_RT start $container_new + $CONTAINER_RT rename $container_old $CONTAINER_NAME-old + $CONTAINER_RT rename $container_new $CONTAINER_NAME - echo -n "Would you like to delete $CONTAINER_NAME-old? (Y/n) " - read -r choice &2 <<-EOF - - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - #-:::::::::::::::::::::::::::::=%@@@@@@@@@@@@@@%=:::::::::::::::::::::::::::::-# - @@*::::::::::::::::::::::::::::::+%@@@@@@@@@@%+::::::::::::::::::::::::::::::*@@ - @@@@+::::::::::::::::::::::::::::::+%@@@@@@%+::::::::::::::::::::::::::::::+@@@@ - @@@@@%=::::::::::::::::::::::::::::::+%@@%+::::::::::::::::::::::::::::::=%@@@@@ - @@@@@@@#-::::::::::::::::::::::::::::::@@::::::::::::::::::::::::::::::-#@@@@@@@ - @@@@@@@@@*:::::::::::::::::::::::::::::@@:::::::::::::::::::::::::::::*@@@@@@@@@ - @@@@@@@@@@%+:::::::::::::::::::::::::::@@:::::::::::::::::::::::::::+%@@@@@@@@@@ - @@@@@@@@@@@@%++++++++++++-:::::::::::::@@:::::::::::::-++++++++++++%@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@#-:::::::::::@@:::::::::::-#@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@*::::::::::@@::::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:::::::::@@:::::::::#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:::::::@@:::::::+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::::@@::::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::@@::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#=@@=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - - - EOF + cat 1>&2 <<-EOF + + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #-:::::::::::::::::::::::::::::=%@@@@@@@@@@@@@@%=:::::::::::::::::::::::::::::-# + @@*::::::::::::::::::::::::::::::+%@@@@@@@@@@%+::::::::::::::::::::::::::::::*@@ + @@@@+::::::::::::::::::::::::::::::+%@@@@@@%+::::::::::::::::::::::::::::::+@@@@ + @@@@@@%=::::::::::::::::::::::::::::::+%@@%+::::::::::::::::::::::::::::::=%@@@@@ + @@@@@@@@#-::::::::::::::::::::::::::::::@@::::::::::::::::::::::::::::::-#@@@@@@@ + @@@@@@@@@@*:::::::::::::::::::::::::::::@@:::::::::::::::::::::::::::::*@@@@@@@@@ + @@@@@@@@@@@@%+:::::::::::::::::::::::::::@@:::::::::::::::::::::::::::+%@@@@@@@@@@ + @@@@@@@@@@@@@@%++++++++++++-:::::::::::::@@:::::::::::::-++++++++++++%@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@#-:::::::::::@@:::::::::::-#@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*::::::::::@@::::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:::::::::@@:::::::::#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:::::::@@:::::::+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::::@@::::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::@@::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#=@@=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + + EOF } setup_monitor_script() { - # 优先使用用户目录,避免权限问题 - local monitor_script="$HOME/.local/bin/monitor_tashi.sh" - local log_file="/tmp/tashi_monitor.log" - - # 确保用户目录存在 - mkdir -p "$HOME/.local/bin" 2>/dev/null || true - - # 如果用户目录创建失败,尝试系统目录(需要 sudo) - if [[ ! -d "$HOME/.local/bin" ]] || [[ ! -w "$HOME/.local/bin" ]]; then - monitor_script="/usr/local/bin/monitor_tashi.sh" - fi - - # 创建监控脚本 - if [[ "$monitor_script" == "/usr/local/bin/monitor_tashi.sh" ]]; then - # 需要 sudo 权限 - ${SUDO_CMD:+"$SUDO_CMD "}bash -c "cat > '$monitor_script'" << 'MONITOR_EOF' + local monitor_script="$HOME/.local/bin/monitor_tashi.sh" + local log_file="/tmp/tashi_monitor.log" + + mkdir -p "$HOME/.local/bin" 2>/dev/null || true + + if [[ ! -d "$HOME/.local/bin" ]] || [[ ! -w "$HOME/.local/bin" ]]; then + monitor_script="/usr/local/bin/monitor_tashi.sh" + fi + + if [[ "$monitor_script" == "/usr/local/bin/monitor_tashi.sh" ]]; then + ${SUDO_CMD:+"$SUDO_CMD "}bash -c "cat > '$monitor_script'" << 'MONITOR_EOF' #!/bin/bash CONTAINER_NAME="tashi-depin-worker" LOG_FILE="/tmp/tashi_monitor.log" -# 检查容器是否存在 if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then exit 0 fi -# 检查容器是否在运行 if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then exit 0 fi -# 检查最近 5 分钟是否有断开连接 if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then - # 检查是否在最近 2 分钟内已经重连成功 if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null docker restart "$CONTAINER_NAME" >/dev/null 2>&1 fi fi MONITOR_EOF - ${SUDO_CMD:+"$SUDO_CMD "}chmod +x "$monitor_script" 2>/dev/null || true - else - # 用户目录,不需要 sudo - cat > "$monitor_script" << 'MONITOR_EOF' + ${SUDO_CMD:+"$SUDO_CMD "}chmod +x "$monitor_script" 2>/dev/null || true + else + cat > "$monitor_script" << 'MONITOR_EOF' #!/bin/bash CONTAINER_NAME="tashi-depin-worker" LOG_FILE="/tmp/tashi_monitor.log" -# 检查容器是否存在 if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then exit 0 fi -# 检查容器是否在运行 if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then exit 0 fi -# 检查最近 5 分钟是否有断开连接 if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then - # 检查是否在最近 2 分钟内已经重连成功 if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null docker restart "$CONTAINER_NAME" >/dev/null 2>&1 fi fi MONITOR_EOF - chmod +x "$monitor_script" 2>/dev/null || true - fi - - # 验证脚本是否创建成功 - if [[ ! -f "$monitor_script" ]]; then - log "WARN" "Failed to create monitor script at $monitor_script" - return 1 - fi - - # 添加到 crontab(每 5 分钟检查一次) - local cron_entry="*/5 * * * * $monitor_script >/dev/null 2>&1" - - # 检查是否已存在,如果存在但路径不同,先删除旧的 - local existing_cron=$(crontab -l 2>/dev/null | grep "monitor_tashi.sh" || true) - if [[ -n "$existing_cron" ]] && [[ "$existing_cron" != *"$monitor_script"* ]]; then - # 删除旧的 crontab 条目 - crontab -l 2>/dev/null | grep -v "monitor_tashi.sh" | crontab - 2>/dev/null || true - fi - - # 如果不存在,添加新的 - if ! crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then - (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - 2>/dev/null || true - fi - - # 验证 crontab 是否添加成功 - if crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then - return 0 - else - log "WARN" "Failed to add monitor script to crontab" - return 1 - fi -} - -post_install() { - echo "" - - log "INFO" "Worker is running: ${CHECKMARK}" - - echo "" - - local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" - local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" - - log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" - log "INFO" "To view the logs of your worker: '$logs_cmd'" - - # 设置监控脚本 - setup_monitor_script - - # 创建桌面快捷方式 - create_desktop_shortcut + chmod +x "$monitor_script" 2>/dev/null || true + fi + + if [[ ! -f "$monitor_script" ]]; then + log "WARN" "Failed to create monitor script at $monitor_script" + return 1 + fi + + local cron_entry="*/5 * * * * $monitor_script >/dev/null 2>&1" + + local existing_cron=$(crontab -l 2>/dev/null | grep "monitor_tashi.sh" || true) + if [[ -n "$existing_cron" ]] && [[ "$existing_cron" != *"$monitor_script"* ]]; then + crontab -l 2>/dev/null | grep -v "monitor_tashi.sh" | crontab - 2>/dev/null || true + fi + + if ! crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - 2>/dev/null || true + fi + + if crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + return 0 + else + log "WARN" "Failed to add monitor script to crontab" + return 1 + fi } create_desktop_shortcut() { - local desktop_path="" - - # 检测桌面路径 - if [[ -n "$HOME" ]]; then - # macOS - if [[ "$OS" == "macos" ]]; then - desktop_path="$HOME/Desktop" - # Linux - 尝试常见的桌面路径 - elif [[ -d "$HOME/Desktop" ]]; then - desktop_path="$HOME/Desktop" - elif [[ -d "$HOME/桌面" ]]; then - desktop_path="$HOME/桌面" - fi - fi - - if [[ -z "$desktop_path" || ! -d "$desktop_path" ]]; then - log "INFO" "Desktop directory not found, skipping shortcut creation." - return - fi - - local shortcut_file="$desktop_path/Tashi.command" - - # 创建快捷方式文件 - cat > "$shortcut_file" <<'SCRIPT_EOF' + local desktop_path="" + + if [[ -n "$HOME" ]]; then + if [[ "$OS" == "macos" ]]; then + desktop_path="$HOME/Desktop" + elif [[ -d "$HOME/Desktop" ]]; then + desktop_path="$HOME/Desktop" + elif [[ -d "$HOME/桌面" ]]; then + desktop_path="$HOME/桌面" + fi + fi + + if [[ -z "$desktop_path" || ! -d "$desktop_path" ]]; then + log "INFO" "Desktop directory not found, skipping shortcut creation." + return + fi + + local shortcut_file="$desktop_path/Tashi.command" + + cat > "$shortcut_file" <<'SCRIPT_EOF' #!/bin/bash # Tashi DePIN Worker restart script -# 设置颜色 GREEN="\033[32m" RED="\033[31m" YELLOW="\033[33m" RESET="\033[0m" -# 配置 CONTAINER_NAME="tashi-depin-worker" AUTH_VOLUME="tashi-depin-worker-auth" AUTH_DIR="/home/worker/auth" @@ -1317,120 +1131,112 @@ IMAGE_TAG="ghcr.io/tashigg/tashi-depin-worker:0" PLATFORM_ARG="--platform linux/amd64" RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" -# ============ 设备检测函数 ============ -# 获取设备唯一标识 get_device_code() { - local device_code="" - - if [[ "$OSTYPE" == "darwin"* ]]; then - if command -v system_profiler >/dev/null 2>&1; then - device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) - fi - if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then - device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') - fi - if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then - device_code=$(sysctl -n hw.serialnumber 2>/dev/null) - fi - else - if [ -f /etc/machine-id ]; then - device_code=$(cat /etc/machine-id 2>/dev/null | xargs) - fi - if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then - device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) - fi - fi - - echo "$device_code" + local device_code="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then + device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then + device_code=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + else + if [ -f /etc/machine-id ]; then + device_code=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + fi + + echo "$device_code" } -# 检查设备状态 check_device_status() { - local device_code="$1" - local server_url="${TASHI_SERVER_URL:-}" - local api_key="${TASHI_API_KEY:-}" - - if [ -z "$server_url" ] || [ -z "$api_key" ]; then - # 尝试使用外部脚本 - local upload_script="" - if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then - upload_script="./upload_devices.sh" - elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then - upload_script="$HOME/rl-swarm/upload_devices.sh" - fi - - if [ -n "$upload_script" ]; then - # 使用外部脚本检查(静默模式) - if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then - return 0 - else - local rc=$? - if [ "$rc" -eq 2 ]; then - return 2 # 设备被禁用 - else - return 0 # 网络错误,允许继续 - fi - fi - else - # 未配置,允许继续 - return 0 - fi - fi - - local status - status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) - - if [ "$status" = "1" ]; then - return 0 - elif [ "$status" = "0" ]; then - return 2 - else - return 0 # 网络错误,允许继续 - fi + local device_code="$1" + local server_url="${TASHI_SERVER_URL:-}" + local api_key="${TASHI_API_KEY:-}" + + if [ -z "$server_url" ] || [ -z "$api_key" ]; then + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + return 2 + else + return 0 + fi + fi + else + return 0 + fi + fi + + local status + status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 0 + fi } perform_device_check() { - local upload_script="" - if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then - upload_script="./upload_devices.sh" - elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then - upload_script="$HOME/rl-swarm/upload_devices.sh" - fi - - if [ -n "$upload_script" ]; then - if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then - return 0 - else - local rc=$? - if [ "$rc" -eq 2 ]; then - exit 2 - else - return 0 - fi - fi - fi - - local device_code=$(get_device_code) - if [ -z "$device_code" ]; then - return 0 - fi - - if check_device_status "$device_code"; then - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - exit 2 - else - return 0 - fi - fi + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi + fi + + local device_code=$(get_device_code) + if [ -z "$device_code" ]; then + return 0 + fi + + if check_device_status "$device_code"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi } -# 切换到脚本所在目录 cd "$(dirname "$0")" || exit 1 -# 清屏 clear perform_device_check >/dev/null 2>&1 @@ -1463,50 +1269,52 @@ fi docker logs -f "$CONTAINER_NAME" SCRIPT_EOF - # 设置执行权限 - chmod +x "$shortcut_file" - - log "INFO" "Desktop shortcut created: $shortcut_file" + chmod +x "$shortcut_file" + + log "INFO" "Desktop shortcut created: $shortcut_file" } -# Detect OS before running checks +post_install() { + echo "" + + log "INFO" "Worker is running: ${CHECKMARK}" + + echo "" + + local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" + local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" + + log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" + log "INFO" "To view the logs of your worker: '$logs_cmd'" + + setup_monitor_script + create_desktop_shortcut +} + +# 主流程开始 detect_os -# ============ 修改点2: 完全跳过设备检查 ============ -# 直接注释掉原来的设备检查代码,不执行任何设备验证 -# log "INFO" "Checking device registration and authorization..." -# -# # 执行设备检测(完全照搬 auto_run.sh 的逻辑) -# setup_device_check -# device_check_rc=$? -# -# # 约定(完全照搬 auto_run.sh): -# # 0 -> 一切正常(已启用,可以继续) -# # 2 -> 设备被禁用或不存在(禁止继续运行) -# # 1/其它 -> 脚本异常(也禁止继续运行) -# log "INFO" "Device check function returned with code: $device_check_rc" -# -# # 根据返回码处理错误 -# if [ "$device_check_rc" -eq 2 ]; then -# log "ERROR" "Device check failed: Device is disabled or not authorized." -# log "INFO" "Please contact administrator to enable your device." -# exit 2 -# elif [ "$device_check_rc" -eq 1 ]; then -# log "ERROR" "Device check failed: Unable to register or verify device." -# log "INFO" "Please check your network connection and try again." -# exit 1 -# fi - -# 添加跳过提示 -log "INFO" "Device registration check has been bypassed (modified script version)" +log "INFO" "Device registration check starting..." + +# 执行设备检测(开发者模式可跳过) +setup_device_check +device_check_rc=$? + +if [ "$device_check_rc" -eq 2 ]; then + log "ERROR" "Device check failed: Device is disabled or not authorized." + log "INFO" "Please contact administrator to enable your device." + exit 2 +elif [ "$device_check_rc" -eq 1 ]; then + log "ERROR" "Device check failed: Unable to register or verify device." + log "INFO" "Please check your network connection and try again." + exit 1 +fi + log "INFO" "Continuing with Docker check..." -# Check Docker (required for installation) -# This must be done before any other checks since Docker is essential log "INFO" "Checking Docker installation and runtime..." check_container_runtime -# Run all checks display_logo log "INFO" "Starting system checks..." @@ -1526,8 +1334,6 @@ check_warnings horizontal_line -# Integrated NAT check. This is separate from system requirements because most manually started worker nodes -# are expected to be behind some sort of NAT, so this is mostly informational. check_nat horizontal_line @@ -1539,11 +1345,11 @@ horizontal_line prompt_continue case "$SUBCOMMAND" in - install) install ;; - update) update ;; - *) - log "ERROR" "BUG: no handler for $($SUBCOMMAND)" - exit 1 + install) install ;; + update) update ;; + *) + log "ERROR" "BUG: no handler for $($SUBCOMMAND)" + exit 1 esac post_install From afbe85784db071c82fb9ebefe0ed3031a48f3a5d Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Thu, 26 Feb 2026 00:41:23 +0800 Subject: [PATCH 12/14] Update Tashi --- Tashi | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Tashi b/Tashi index b9ec5ac..c6f03ad 100644 --- a/Tashi +++ b/Tashi @@ -1093,6 +1093,7 @@ MONITOR_EOF fi } +# 修复:确保 heredoc 正确结束 create_desktop_shortcut() { local desktop_path="" @@ -1113,7 +1114,8 @@ create_desktop_shortcut() { local shortcut_file="$desktop_path/Tashi.command" - cat > "$shortcut_file" <<'SCRIPT_EOF' + # 修复:确保 heredoc 正确结束 + cat > "$shortcut_file" << 'SCRIPT_EOF' #!/bin/bash # Tashi DePIN Worker restart script @@ -1261,29 +1263,22 @@ if docker run -d \ "$IMAGE_TAG" \ run "$AUTH_DIR" \ --unstable-update-download-path /tmp/tashi-depin-worker; then - : + docker logs -f "$CONTAINER_NAME" else exit 1 fi - -docker logs -f "$CONTAINER_NAME" SCRIPT_EOF chmod +x "$shortcut_file" - log "INFO" "Desktop shortcut created: $shortcut_file" } post_install() { echo "" - log "INFO" "Worker is running: ${CHECKMARK}" - echo "" - local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" - log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" log "INFO" "To view the logs of your worker: '$logs_cmd'" From ef93d10307300ba5afe2be2caab4097eac783283 Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Thu, 26 Feb 2026 01:03:52 +0800 Subject: [PATCH 13/14] Update and rename Tashi to tashi/deploy_tashi.sh --- Tashi | 1350 --------------------------------------- tashi/deploy_tashi.sh | 1387 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1387 insertions(+), 1350 deletions(-) delete mode 100644 Tashi create mode 100644 tashi/deploy_tashi.sh diff --git a/Tashi b/Tashi deleted file mode 100644 index c6f03ad..0000000 --- a/Tashi +++ /dev/null @@ -1,1350 +0,0 @@ -#!/usr/bin/env bash -# shellcheck disable=SC2155,SC2181 - -IMAGE_TAG='ghcr.io/tashigg/tashi-depin-worker:0' - -TROUBLESHOOT_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#troubleshooting' -MANUAL_UPDATE_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#manual-update' - -DOCKER_ROOTLESS_LINK='https://docs.docker.com/engine/install/linux-postinstall/' -PODMAN_ROOTLESS_LINK='https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md' - -RUST_LOG='info,tashi_depin_worker=debug,tashi_depin_common=debug' - -AGENT_PORT=39065 - -# Color codes -GREEN="\e[32m" -RED="\e[31m" -YELLOW="\e[33m" -BLUE="\e[34m" -CYAN="\e[36m" -RESET="\e[0m" -CHECKMARK="${GREEN}✓${RESET}" -CROSSMARK="${RED}✗${RESET}" -WARNING="${YELLOW}⚠${RESET}" -INFO="${BLUE}ℹ${RESET}" - -STYLE_BOLD=$(tput bold) -STYLE_NORMAL=$(tput sgr0) - -WARNINGS=0 -ERRORS=0 - -# 开发者模式开关(默认关闭) -DEV_MODE=false - -# 解析命令行参数 -while [[ $# -gt 0 ]]; do - case $1 in - --dev-mode|--skip-device-check) - DEV_MODE=true - echo -e "${YELLOW}⚠️ 开发者模式:跳过设备检查${RESET}" - shift - ;; - --help) - echo "使用方法: ./install.sh [选项]" - echo "选项:" - echo " --dev-mode 开发者模式(跳过设备检查)" - echo " --help 显示此帮助信息" - exit 0 - ;; - *) - shift - ;; - esac -done - -# Logging function -log() { - local message="${2:-$(cat)}" - if [[ "${LOG_EXPANDED:-0}" -ne 0 ]]; then - local level="$1" - local timestamp=$(date +"%Y-%m-%d %H:%M:%S") - printf "[%s] [%s] %b\n" "${timestamp}" "${level}" "${message}" 1>&2 - else - printf "%b\n" "$message" - fi -} - -make_bold() { - local s="${1:-$(cat)}" - printf "%s%s%s" "$STYLE_BOLD" "${s}" "$STYLE_NORMAL" -} - -horizontal_line() { - WIDTH=${COLUMNS:-$(tput cols)} - FILL_CHAR='-' - printf '\n%*s\n\n' "$WIDTH" '' | tr ' ' "$FILL_CHAR" -} - -# Detect OS safely -detect_os() { - OS=$( - source /etc/os-release >/dev/null 2>&1 - echo "${ID:-unknown}" - ) - if [[ "$OS" == "unknown" && "$(uname -s)" == "Darwin" ]]; then - OS="macos" - fi -} - -suggest_install() { - local package=$1 - case "$OS" in - debian | ubuntu) echo " sudo apt update && sudo apt install -y $package" ;; - fedora) echo " sudo dnf install -y $package" ;; - arch) echo " sudo pacman -S --noconfirm $package" ;; - opensuse) echo " sudo zypper install -y $package" ;; - macos) echo " brew install $package" ;; - *) echo " Please install '$package' manually for your OS." ;; - esac -} - -NPROC_CMD=$(command -v nproc || echo "") -GREP_CMD=$(command -v grep || echo "") -DF_CMD=$(command -v df || echo "") - -check_command() { - command -v "$1" >/dev/null 2>&1 -} - -check_platform() { - PLATFORM_ARG='' - local arch=$(uname -m) - - if [[ "$arch" == "amd64" || "$arch" == "x86_64" ]]; then - log "INFO" "Platform Check: ${CHECKMARK} supported platform $arch" - elif [[ "$OS" == "macos" && "$arch" == arm64 ]]; then - PLATFORM_ARG='--platform linux/amd64' - log "WARNING" "Platform Check: ${WARNING} unsupported platform $arch" - log "INFO" <<-EOF - MacOS Apple Silicon is not currently supported, but the worker can still run through the Rosetta compatibility layer. - Performance and earnings will be less than a native node. - You may be prompted to install Rosetta when the worker node starts. - EOF - ((WARNINGS++)) - else - log "ERROR" "Platform Check: ${CROSSMARK} unsupported platform $arch" - log "INFO" "Join the Tashi Discord to request support for your system." - ((ERRORS++)) - return - fi -} - -check_cpu() { - case "$OS" in - "macos") - threads=$(sysctl -n hw.ncpu) - ;; - *) - if [[ -z "$NPROC_CMD" ]]; then - log "WARNING" "'nproc' not found. Install coreutils:" - suggest_install "coreutils" - ((ERRORS++)) - return - fi - threads=$("$NPROC_CMD") - ;; - esac - - if [[ "$threads" -ge 4 ]]; then - log "INFO" "CPU Check: ${CHECKMARK} Found $threads threads (>= 4 recommended)" - elif [[ "$threads" -ge 2 ]]; then - log "WARNING" "CPU Check: ${WARNING} Found $threads threads (>= 2 required, 4 recommended)" - ((WARNINGS++)) - else - log "ERROR" "CPU Check: ${CROSSMARK} Only $threads threads found (Minimum: 2 required)" - ((ERRORS++)) - fi -} - -check_memory() { - if [[ -z "$GREP_CMD" ]]; then - log "ERROR" "Memory Check: ${WARNING} 'grep' not found. Install grep:" - suggest_install "grep" - ((ERRORS++)) - return - fi - - case "$OS" in - "macos") - total_mem_bytes=$(sysctl -n hw.memsize) - total_mem_kb=$((total_mem_bytes / 1024)) - ;; - *) - total_mem_kb=$("$GREP_CMD" MemTotal /proc/meminfo | awk '{print $2}') - ;; - esac - - total_mem_gb=$((total_mem_kb / 1024 / 1024)) - - if [[ "$total_mem_gb" -ge 4 ]]; then - log "INFO" "Memory Check: ${CHECKMARK} Found ${total_mem_gb}GB RAM (>= 4GB recommended)" - elif [[ "$total_mem_gb" -ge 2 ]]; then - log "WARNING" "Memory Check: ${WARNING} Found ${total_mem_gb}GB RAM (>= 2GB required, 4GB recommended)" - ((WARNINGS++)) - else - log "ERROR" "Memory Check: ${CROSSMARK} Only ${total_mem_gb}GB RAM found (Minimum: 2GB required)" - ((ERRORS++)) - fi -} - -check_disk() { - case "$OS" in - "macos") - available_disk_kb=$( - "$DF_CMD" -kcI 2>/dev/null | - tail -1 | - awk '{print $4}' - ) - ;; - *) - available_disk_kb=$( - "$DF_CMD" -kx tmpfs --total 2>/dev/null | - tail -1 | - awk '{print $4}' - ) - ;; - esac - - available_disk_gb=$((available_disk_kb / 1024 / 1024)) - - if [[ "$available_disk_gb" -ge 20 ]]; then - log "INFO" "Disk Space Check: ${CHECKMARK} Found ${available_disk_gb}GB free (>= 20GB required)" - else - log "ERROR" "Disk Space Check: ${CROSSMARK} Only ${available_disk_gb}GB free space (Minimum: 20GB required)" - ((ERRORS++)) - fi -} - -check_container_runtime() { - detect_os - - if check_command "docker"; then - log "INFO" "Container Runtime Check: ${CHECKMARK} Docker is installed" - CONTAINER_RT=docker - - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is running" - else - log "WARNING" "Docker Runtime Check: ${WARNING} Docker is installed but not running" - - if [[ "$OS" == "macos" ]]; then - log "INFO" "Attempting to start Docker Desktop..." - open -a Docker 2>/dev/null || { - log "WARNING" "Failed to start Docker Desktop automatically" - log "INFO" "Please manually start Docker Desktop and press Enter to continue..." - read -r - } - - log "INFO" "Waiting for Docker Desktop to start..." - local waited=0 - local max_wait=60 - while [ $waited -lt $max_wait ]; do - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" - break - fi - sleep 2 - waited=$((waited + 2)) - echo -n "." - done - echo "" - - if ! docker info >/dev/null 2>&1; then - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker failed to start after ${max_wait} seconds" - log "INFO" "Please ensure Docker Desktop is running and try again" - ((ERRORS++)) - fi - else - if command -v systemctl >/dev/null 2>&1; then - log "INFO" "Attempting to start Docker service..." - if sudo systemctl start docker 2>/dev/null; then - sleep 3 - if docker info >/dev/null 2>&1; then - log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker service failed to start" - ((ERRORS++)) - fi - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Failed to start Docker service" - log "INFO" "Please manually start Docker service: sudo systemctl start docker" - ((ERRORS++)) - fi - else - log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker is not running and cannot be started automatically" - ((ERRORS++)) - fi - fi - fi - elif check_command "podman"; then - log "INFO" "Container Runtime Check: ${CHECKMARK} Podman is installed" - CONTAINER_RT=podman - else - log "ERROR" "Container Runtime Check: ${CROSSMARK} Neither Docker nor Podman is installed." - ((ERRORS++)) - fi -} - -check_internet() { - if curl -s --head --connect-timeout 3 https://google.com | grep "HTTP" >/dev/null 2>&1; then - log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." - elif wget --spider --timeout=3 --quiet https://google.com; then - log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." - else - log "ERROR" "Internet Connectivity: ${CROSSMARK} No internet access detected!" - ((ERRORS++)) - fi -} - -get_local_ip() { - if [[ "$OS" == "macos" ]]; then - LOCAL_IP=$(ifconfig -l | xargs -n1 ipconfig getifaddr) - elif check_command hostname; then - LOCAL_IP=$(hostname -I | awk '{print $1}') - elif check_command ip; then - LOCAL_IP=$(ip route get '1.0.0.0' | grep -Po "src \K(\S+)") - fi -} - -get_public_ip() { - PUBLIC_IP=$(curl -s https://api.ipify.org || wget -qO- https://api.ipify.org) -} - -check_nat() { - local nat_message=$( - cat <<-EOF - If this device is not accessible from the Internet, some DePIN services will be disabled; - earnings may be less than a publicly accessible node. - - For maximum earning potential, ensure UDP port $AGENT_PORT is forwarded to this device. - Consult your router’s manual or contact your Internet Service Provider for details. - EOF - ); - - get_local_ip - get_public_ip - - if [[ -z "$LOCAL_IP" ]]; then - log "WARNING" "NAT Check: ${WARNING} Could not determine local IP." - log "WARNING" "$nat_message" - return - fi - - if [[ -z "$PUBLIC_IP" ]]; then - log "WARNING" "NAT Check: ${WARNING} Could not determine public IP." - log "WARNING" "$nat_message" - return - fi - - if [[ "$LOCAL_IP" == "$PUBLIC_IP" ]]; then - log "INFO" "NAT Check: ${CHECKMARK} Open NAT / Publicly accessible (Public IP: $PUBLIC_IP)" - return - fi - - log "WARNING" "NAT Check: NAT detected (Local: $LOCAL_IP, Public: $PUBLIC_IP)" - log "WARNING" "$nat_message" -} - -check_root_required() { - if [[ "$OS" == "macos" ]]; then - SUDO_CMD='' - log "INFO" "Privilege Check: ${CHECKMARK} Root privileges are not needed on MacOS" - return - fi - - if [[ "$CONTAINER_RT" == "docker" ]]; then - if (groups "$USER" | grep docker >/dev/null); then - log "INFO" "Privilege Check: ${CHECKMARK} User is in 'docker' group." - log "INFO" "Worker container can be started without needing superuser privileges." - elif [[ -w "$DOCKER_HOST" ]] || [[ -w "/var/run/docker.sock" ]]; then - log "INFO" "Privilege Check: ${CHECKMARK} User has access to the Docker daemon socket." - log "INFO" "Worker container can be started without needing superuser privileges." - else - SUDO_CMD="sudo -g docker" - log "WARNING" "Privilege Check: ${WARNING} User is not in 'docker' group." - log "WARNING" <<-EOF - ${WARNING} 'docker run' command will be executed using '${SUDO_CMD}' - You may be prompted for your password during setup. - - Rootless configuration is recommended to avoid this requirement. - For more information, see $DOCKER_ROOTLESS_LINK - EOF - ((WARNINGS++)) - fi - elif [[ "$CONTAINER_RT" == "podman" ]]; then - if (grep "^$USER:" /etc/subuid >/dev/null) && (grep "^$(id -gn):" /etc/subgid >/dev/null); then - log "INFO" "Privilege Check: ${CHECKMARK} User can create Podman containers without root." - log "INFO" "Worker container can be started without needing superuser privileges." - else - SUDO_CMD="sudo" - log "WARNING" "Privilege Check: ${WARNING} User cannot create rootless Podman containers." - log "WARNING" <<-EOF - ${WARNING} 'podman run' command will be executed using '${SUDO_CMD}' - You may be prompted for your sudo password during setup. - - Rootless configuration is recommended to avoid this requirement. - For more information, see $PODMAN_ROOTLESS_LINK - EOF - ((WARNINGS++)) - fi - fi -} - -prompt_auto_updates() { - log "INFO" <<-EOF - Your DePIN worker will require periodic updates to ensure that it keeps up with new features and bug fixes. - Out-of-date workers may be excluded from the DePIN network and be unable to complete jobs or earn rewards. - - We recommend enabling automatic updates, which take place entirely in the container - and do not make any changes to your system. - - Otherwise, you will need to check the worker logs regularly to see when a new update is available, - and apply the update manually.\n - EOF - - log "INFO" "Automatic updates enabled (default: yes)." - AUTO_UPDATE=y - echo "" -} - -prompt() { - local prompt="${1?}" - local variable="${2?}" - printf "%b" "$prompt" - read -r "${variable?}" /dev/null 2>&1; then - return 1 - fi - - python3 -c " -import base64 -import sys - -encrypted = '$encrypted' -key = 'RL_SWARM_2024' - -try: - decoded = base64.b64decode(encrypted) - result = bytearray() - key_bytes = key.encode('utf-8') - for i, byte in enumerate(decoded): - result.append(byte ^ key_bytes[i % len(key_bytes)]) - print(result.decode('utf-8')) -except Exception as e: - sys.exit(1) -" 2>/dev/null -} - -get_device_code() { - local serial="" - - if [[ "$OSTYPE" == "darwin"* ]]; then - if command -v system_profiler >/dev/null 2>&1; then - serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) - fi - - if [ -z "$serial" ]; then - if command -v ioreg >/dev/null 2>&1; then - serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') - fi - fi - - if [ -z "$serial" ]; then - if command -v sysctl >/dev/null 2>&1; then - serial=$(sysctl -n hw.serialnumber 2>/dev/null) - fi - fi - else - if [ -f /etc/machine-id ]; then - serial=$(cat /etc/machine-id 2>/dev/null | xargs) - fi - - if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then - serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) - fi - - if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then - serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) - fi - fi - - echo "$serial" -} - -get_current_user() { - local user="" - - if [ -n "$USER" ]; then - user="$USER" - elif command -v whoami >/dev/null 2>&1; then - user=$(whoami) - elif command -v id >/dev/null 2>&1; then - user=$(id -un) - fi - - echo "$user" -} - -build_json() { - local customer_name="$1" - local device_code="$2" - - echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" -} - -get_server_config() { - local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" - local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" - - if [ -n "$TASHI_SERVER_URL" ]; then - SERVER_URL="$TASHI_SERVER_URL" - log "INFO" "Using SERVER_URL from TASHI_SERVER_URL environment variable" - elif [ -n "$SERVER_URL" ]; then - log "INFO" "Using SERVER_URL from SERVER_URL environment variable" - : - else - log "INFO" "Decrypting SERVER_URL from encrypted default..." - if ! command -v python3 >/dev/null 2>&1; then - log "WARNING" "python3 not found, cannot decrypt default SERVER_URL" - SERVER_URL="" - else - SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") - fi - fi - - if [ -n "$TASHI_API_KEY" ]; then - API_KEY="$TASHI_API_KEY" - log "INFO" "Using API_KEY from TASHI_API_KEY environment variable" - elif [ -n "$API_KEY" ]; then - log "INFO" "Using API_KEY from API_KEY environment variable" - : - else - log "INFO" "Decrypting API_KEY from encrypted default..." - if ! command -v python3 >/dev/null 2>&1; then - log "WARNING" "python3 not found, cannot decrypt default API_KEY" - API_KEY="" - else - API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") - fi - fi - - export SERVER_URL API_KEY - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - log "INFO" "Server configuration not available, device check will be skipped" - fi -} - -check_device_status() { - local device_code="$1" - - get_server_config - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - return 0 - fi - - local status - status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") - - if [ "$status" = "1" ]; then - return 0 - elif [ "$status" = "0" ]; then - return 2 - else - return 1 - fi -} - -upload_device_info() { - local device_code="$1" - local customer_name="$2" - - get_server_config - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - return 1 - fi - - local devices_json - devices_json=$(build_json "$customer_name" "$device_code") - - local response - response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ - -H "Content-Type: application/json" \ - -d "{ - \"api_key\": \"$API_KEY\", - \"devices\": $devices_json - }") - - if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then - return 0 - else - return 1 - fi -} - -setup_device_check() { - # 开发者模式:跳过设备检查 - if [ "$DEV_MODE" = true ]; then - log "INFO" "Developer mode: Device check bypassed" - return 0 - fi - - get_server_config - - if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then - log "WARNING" "Server URL or API key not configured, skipping device check" - return 0 - fi - - local STATE_FILE="$HOME/.device_registered" - if [ -z "$HOME" ] && [ -n "$USERPROFILE" ]; then - STATE_FILE="$USERPROFILE/.device_registered" - elif [ -z "$HOME" ] && [ -z "$USERPROFILE" ]; then - STATE_FILE=".device_registered" - fi - - local SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - local OLD_STATE_FILE="$SCRIPT_DIR/.device_registered" - if [ -f "$OLD_STATE_FILE" ] && [ ! -f "$STATE_FILE" ]; then - cp "$OLD_STATE_FILE" "$STATE_FILE" 2>/dev/null || true - fi - - local DEVICE_CODE - DEVICE_CODE=$(get_device_code) - - if [ -z "$DEVICE_CODE" ]; then - log "WARNING" "Could not get device code, skipping device check" - return 0 - fi - - if [ -f "$STATE_FILE" ]; then - local SAVED_CODE - SAVED_CODE=$(grep '^device_code=' "$STATE_FILE" 2>/dev/null | cut -d'=' -f2-) - if [ -n "$SAVED_CODE" ] && [ "$SAVED_CODE" = "$DEVICE_CODE" ]; then - if check_device_status "$DEVICE_CODE"; then - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - log "ERROR" "Device is disabled. Installation aborted." - return 2 - else - return 0 - fi - fi - fi - fi - - local DEFAULT_CUSTOMER - DEFAULT_CUSTOMER=$(get_current_user) - - local CUSTOMER_NAME="" - if [ "${SKIP_CONFIRM:-false}" != "true" ]; then - read -p "请输入客户名称 (直接回车使用默认: $DEFAULT_CUSTOMER): " CUSTOMER_NAME - else - CUSTOMER_NAME="${CUSTOMER_NAME:-$DEFAULT_CUSTOMER}" - fi - - if [ -z "$CUSTOMER_NAME" ]; then - CUSTOMER_NAME="$DEFAULT_CUSTOMER" - fi - - CUSTOMER_NAME=$(echo "$CUSTOMER_NAME" | xargs) - - if [ -z "$CUSTOMER_NAME" ]; then - log "ERROR" "Customer name cannot be empty. Installation aborted." - return 1 - fi - - local devices_json - devices_json=$(build_json "$CUSTOMER_NAME" "$DEVICE_CODE") - - local response - response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ - -H "Content-Type: application/json" \ - -d "{ - \"api_key\": \"$API_KEY\", - \"devices\": $devices_json - }") - - if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then - if check_device_status "$DEVICE_CODE"; then - { - echo "device_code=$DEVICE_CODE" - echo "customer_name=$CUSTOMER_NAME" - echo "uploaded_at=$(date '+%Y-%m-%d %H:%M:%S')" - } > "$STATE_FILE" 2>/dev/null || true - - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - log "ERROR" "Device is disabled after registration. Installation aborted." - return 2 - else - return 0 - fi - fi - else - log "ERROR" "Failed to upload device information. Installation aborted." - return 1 - fi -} - -check_and_stop_existing_container() { - if ${CONTAINER_RT} ps -a --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then - log "INFO" "Found existing container: ${CONTAINER_NAME}" - - if ${CONTAINER_RT} ps --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then - log "INFO" "Stopping running container..." - ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} stop "$CONTAINER_NAME" >/dev/null 2>&1 - fi - - log "INFO" "Removing existing container..." - ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} rm "$CONTAINER_NAME" >/dev/null 2>&1 - - log "INFO" "Existing container removed: ${CHECKMARK}" - fi -} - -# 新增:等待用户完成授权并输入token的函数 -wait_for_authorization() { - echo "" - echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" - echo -e "${YELLOW}🔐 Tashi DePIN Worker 需要授权${RESET}" - echo -e "${CYAN}══════════════════════════════════════════════════════════════════${RESET}" - echo "" - echo -e "请按以下步骤完成授权:" - echo "" - echo -e "1. ${GREEN}复制上面的链接${RESET}(如果看不清,可以在日志中找到)" - echo -e "2. ${GREEN}在浏览器中打开该链接${RESET}" - echo -e "3. ${GREEN}连接已质押 $TASHI 的钱包完成绑定${RESET}" - echo -e "4. ${GREEN}获取授权令牌并粘贴到下方${RESET}" - echo "" - echo -e "${YELLOW}提示:粘贴令牌后按回车继续${RESET}" - echo "" - - # 等待用户输入token - local token="" - while [ -z "$token" ]; do - read -p "👉 请输入授权令牌: " token &1 | tee "$temp_output" - echo "${PIPESTATUS[0]}" > "$temp_token" - ) & - local setup_pid=$! - - # 等待并检查输出中是否包含授权提示 - local auth_prompt_shown=false - local max_wait=300 # 最多等待5分钟 - local waited=0 - - while [ $waited -lt $max_wait ]; do - if grep -q "paste the authorization token below" "$temp_output" 2>/dev/null; then - if [ "$auth_prompt_shown" = false ]; then - auth_prompt_shown=true - wait_for_authorization - - # 将token通过管道发送给setup命令 - echo "$AUTH_TOKEN" > /dev/stdin - fi - fi - - if ! kill -0 $setup_pid 2>/dev/null; then - # 进程已结束 - break - fi - - sleep 1 - waited=$((waited + 1)) - done - - wait $setup_pid - local exit_code=$(cat "$temp_token") - - rm -f "$temp_output" "$temp_token" - - echo "" - - if [[ $exit_code -eq 130 ]]; then - log "INFO" "Worker setup cancelled. You may re-run this script at any time." - exit 0 - elif [[ $exit_code -ne 0 ]]; then - log "ERROR" "Setup failed ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." - exit 1 - fi - - local run_cmd=$(make_run_cmd) - - sh -c "set -ex; $run_cmd" - - exit_code=$? - - echo "" - - if [[ $exit_code -ne 0 ]]; then - log "ERROR" "Worker failed to start ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." - - local logs_output=$(docker logs "$CONTAINER_NAME" 2>&1 | tail -5) - if echo "$logs_output" | grep -q "node_auth.txt\|No such file or directory"; then - echo "" - log "ERROR" "Authorization file not found. This usually means:" - log "ERROR" " 1. The interactive setup was not completed" - log "ERROR" " 2. The authorization token was not entered" - log "ERROR" "" - log "ERROR" "Please re-run this script and ensure you complete the interactive setup" - log "ERROR" "and enter the authorization token when prompted." - fi - fi -} - -update() { - log "INFO" "Updating worker. The commands being run will be printed for transparency.\n" - - local container_old="$CONTAINER_NAME" - local container_new="$CONTAINER_NAME-new" - - local create_cmd=$(make_run_cmd "" "create" "$container_new" "$container_old") - - ${SUDO_CMD+"$SUDO_CMD "}bash <<-EOF - set -x - - ($CONTAINER_RT inspect "$CONTAINER_NAME-old" >/dev/null 2>&1) - - if [ \$? -eq 0 ]; then - echo "$CONTAINER_NAME-old already exists (presumably from a failed run), please delete it before continuing" 1>&2 - exit 1 - fi - - ($CONTAINER_RT inspect "$container_new" >/dev/null 2>&1) - - if [ \$? -eq 0 ]; then - echo "$container_new already exists (presumably from a failed run), please delete it before continuing" 1>&2 - exit 1 - fi - - set -ex - - $create_cmd - $CONTAINER_RT stop $container_old - $CONTAINER_RT start $container_new - $CONTAINER_RT rename $container_old $CONTAINER_NAME-old - $CONTAINER_RT rename $container_new $CONTAINER_NAME - - echo -n "Would you like to delete $CONTAINER_NAME-old? (Y/n) " - read -r choice &2 <<-EOF - - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - #-:::::::::::::::::::::::::::::=%@@@@@@@@@@@@@@%=:::::::::::::::::::::::::::::-# - @@*::::::::::::::::::::::::::::::+%@@@@@@@@@@%+::::::::::::::::::::::::::::::*@@ - @@@@+::::::::::::::::::::::::::::::+%@@@@@@%+::::::::::::::::::::::::::::::+@@@@ - @@@@@@%=::::::::::::::::::::::::::::::+%@@%+::::::::::::::::::::::::::::::=%@@@@@ - @@@@@@@@#-::::::::::::::::::::::::::::::@@::::::::::::::::::::::::::::::-#@@@@@@@ - @@@@@@@@@@*:::::::::::::::::::::::::::::@@:::::::::::::::::::::::::::::*@@@@@@@@@ - @@@@@@@@@@@@%+:::::::::::::::::::::::::::@@:::::::::::::::::::::::::::+%@@@@@@@@@@ - @@@@@@@@@@@@@@%++++++++++++-:::::::::::::@@:::::::::::::-++++++++++++%@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@#-:::::::::::@@:::::::::::-#@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*::::::::::@@::::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:::::::::@@:::::::::#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:::::::@@:::::::+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::::@@::::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::@@::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#=@@=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - - - EOF -} - -setup_monitor_script() { - local monitor_script="$HOME/.local/bin/monitor_tashi.sh" - local log_file="/tmp/tashi_monitor.log" - - mkdir -p "$HOME/.local/bin" 2>/dev/null || true - - if [[ ! -d "$HOME/.local/bin" ]] || [[ ! -w "$HOME/.local/bin" ]]; then - monitor_script="/usr/local/bin/monitor_tashi.sh" - fi - - if [[ "$monitor_script" == "/usr/local/bin/monitor_tashi.sh" ]]; then - ${SUDO_CMD:+"$SUDO_CMD "}bash -c "cat > '$monitor_script'" << 'MONITOR_EOF' -#!/bin/bash -CONTAINER_NAME="tashi-depin-worker" -LOG_FILE="/tmp/tashi_monitor.log" - -if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then - exit 0 -fi - -if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then - exit 0 -fi - -if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then - if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then - echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null - docker restart "$CONTAINER_NAME" >/dev/null 2>&1 - fi -fi -MONITOR_EOF - ${SUDO_CMD:+"$SUDO_CMD "}chmod +x "$monitor_script" 2>/dev/null || true - else - cat > "$monitor_script" << 'MONITOR_EOF' -#!/bin/bash -CONTAINER_NAME="tashi-depin-worker" -LOG_FILE="/tmp/tashi_monitor.log" - -if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then - exit 0 -fi - -if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then - exit 0 -fi - -if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then - if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then - echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null - docker restart "$CONTAINER_NAME" >/dev/null 2>&1 - fi -fi -MONITOR_EOF - chmod +x "$monitor_script" 2>/dev/null || true - fi - - if [[ ! -f "$monitor_script" ]]; then - log "WARN" "Failed to create monitor script at $monitor_script" - return 1 - fi - - local cron_entry="*/5 * * * * $monitor_script >/dev/null 2>&1" - - local existing_cron=$(crontab -l 2>/dev/null | grep "monitor_tashi.sh" || true) - if [[ -n "$existing_cron" ]] && [[ "$existing_cron" != *"$monitor_script"* ]]; then - crontab -l 2>/dev/null | grep -v "monitor_tashi.sh" | crontab - 2>/dev/null || true - fi - - if ! crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then - (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - 2>/dev/null || true - fi - - if crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then - return 0 - else - log "WARN" "Failed to add monitor script to crontab" - return 1 - fi -} - -# 修复:确保 heredoc 正确结束 -create_desktop_shortcut() { - local desktop_path="" - - if [[ -n "$HOME" ]]; then - if [[ "$OS" == "macos" ]]; then - desktop_path="$HOME/Desktop" - elif [[ -d "$HOME/Desktop" ]]; then - desktop_path="$HOME/Desktop" - elif [[ -d "$HOME/桌面" ]]; then - desktop_path="$HOME/桌面" - fi - fi - - if [[ -z "$desktop_path" || ! -d "$desktop_path" ]]; then - log "INFO" "Desktop directory not found, skipping shortcut creation." - return - fi - - local shortcut_file="$desktop_path/Tashi.command" - - # 修复:确保 heredoc 正确结束 - cat > "$shortcut_file" << 'SCRIPT_EOF' -#!/bin/bash - -# Tashi DePIN Worker restart script - -GREEN="\033[32m" -RED="\033[31m" -YELLOW="\033[33m" -RESET="\033[0m" - -CONTAINER_NAME="tashi-depin-worker" -AUTH_VOLUME="tashi-depin-worker-auth" -AUTH_DIR="/home/worker/auth" -AGENT_PORT=39065 -IMAGE_TAG="ghcr.io/tashigg/tashi-depin-worker:0" -PLATFORM_ARG="--platform linux/amd64" -RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" - -get_device_code() { - local device_code="" - - if [[ "$OSTYPE" == "darwin"* ]]; then - if command -v system_profiler >/dev/null 2>&1; then - device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) - fi - if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then - device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') - fi - if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then - device_code=$(sysctl -n hw.serialnumber 2>/dev/null) - fi - else - if [ -f /etc/machine-id ]; then - device_code=$(cat /etc/machine-id 2>/dev/null | xargs) - fi - if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then - device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) - fi - fi - - echo "$device_code" -} - -check_device_status() { - local device_code="$1" - local server_url="${TASHI_SERVER_URL:-}" - local api_key="${TASHI_API_KEY:-}" - - if [ -z "$server_url" ] || [ -z "$api_key" ]; then - local upload_script="" - if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then - upload_script="./upload_devices.sh" - elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then - upload_script="$HOME/rl-swarm/upload_devices.sh" - fi - - if [ -n "$upload_script" ]; then - if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then - return 0 - else - local rc=$? - if [ "$rc" -eq 2 ]; then - return 2 - else - return 0 - fi - fi - else - return 0 - fi - fi - - local status - status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) - - if [ "$status" = "1" ]; then - return 0 - elif [ "$status" = "0" ]; then - return 2 - else - return 0 - fi -} - -perform_device_check() { - local upload_script="" - if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then - upload_script="./upload_devices.sh" - elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then - upload_script="$HOME/rl-swarm/upload_devices.sh" - fi - - if [ -n "$upload_script" ]; then - if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then - return 0 - else - local rc=$? - if [ "$rc" -eq 2 ]; then - exit 2 - else - return 0 - fi - fi - fi - - local device_code=$(get_device_code) - if [ -z "$device_code" ]; then - return 0 - fi - - if check_device_status "$device_code"; then - return 0 - else - local status_rc=$? - if [ "$status_rc" -eq 2 ]; then - exit 2 - else - return 0 - fi - fi -} - -cd "$(dirname "$0")" || exit 1 - -clear - -perform_device_check >/dev/null 2>&1 - -if docker stop "$CONTAINER_NAME" >/dev/null 2>&1; then - docker rm "$CONTAINER_NAME" >/dev/null 2>&1 -fi - -if docker run -d \ - -p "$AGENT_PORT:$AGENT_PORT" \ - -p 127.0.0.1:9000:9000 \ - --mount type=volume,src="$AUTH_VOLUME",dst="$AUTH_DIR" \ - --name "$CONTAINER_NAME" \ - -e RUST_LOG="$RUST_LOG" \ - --health-cmd='pgrep -f tashi-depin-worker || exit 1' \ - --health-interval=30s \ - --health-timeout=10s \ - --health-retries=3 \ - --restart=unless-stopped \ - --pull=always \ - $PLATFORM_ARG \ - "$IMAGE_TAG" \ - run "$AUTH_DIR" \ - --unstable-update-download-path /tmp/tashi-depin-worker; then - docker logs -f "$CONTAINER_NAME" -else - exit 1 -fi -SCRIPT_EOF - - chmod +x "$shortcut_file" - log "INFO" "Desktop shortcut created: $shortcut_file" -} - -post_install() { - echo "" - log "INFO" "Worker is running: ${CHECKMARK}" - echo "" - local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" - local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" - log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" - log "INFO" "To view the logs of your worker: '$logs_cmd'" - - setup_monitor_script - create_desktop_shortcut -} - -# 主流程开始 -detect_os - -log "INFO" "Device registration check starting..." - -# 执行设备检测(开发者模式可跳过) -setup_device_check -device_check_rc=$? - -if [ "$device_check_rc" -eq 2 ]; then - log "ERROR" "Device check failed: Device is disabled or not authorized." - log "INFO" "Please contact administrator to enable your device." - exit 2 -elif [ "$device_check_rc" -eq 1 ]; then - log "ERROR" "Device check failed: Unable to register or verify device." - log "INFO" "Please check your network connection and try again." - exit 1 -fi - -log "INFO" "Continuing with Docker check..." - -log "INFO" "Checking Docker installation and runtime..." -check_container_runtime - -display_logo - -log "INFO" "Starting system checks..." - -echo "" - -check_platform -check_cpu -check_memory -check_disk -check_root_required -check_internet - -echo "" - -check_warnings - -horizontal_line - -check_nat - -horizontal_line - -prompt_auto_updates - -horizontal_line - -prompt_continue - -case "$SUBCOMMAND" in - install) install ;; - update) update ;; - *) - log "ERROR" "BUG: no handler for $($SUBCOMMAND)" - exit 1 -esac - -post_install diff --git a/tashi/deploy_tashi.sh b/tashi/deploy_tashi.sh new file mode 100644 index 0000000..d6514c3 --- /dev/null +++ b/tashi/deploy_tashi.sh @@ -0,0 +1,1387 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2155,SC2181 + +IMAGE_TAG='ghcr.io/tashigg/tashi-depin-worker:0' + +TROUBLESHOOT_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#troubleshooting' +MANUAL_UPDATE_LINK='https://docs.tashi.network/nodes/node-installation/important-notes#manual-update' + +DOCKER_ROOTLESS_LINK='https://docs.docker.com/engine/install/linux-postinstall/' +PODMAN_ROOTLESS_LINK='https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md' + +RUST_LOG='info,tashi_depin_worker=debug,tashi_depin_common=debug' + +AGENT_PORT=39065 + +# Color codes +GREEN="\e[32m" +RED="\e[31m" +YELLOW="\e[33m" +RESET="\e[0m" +CHECKMARK="${GREEN}✓${RESET}" +CROSSMARK="${RED}✗${RESET}" +WARNING="${YELLOW}⚠${RESET}" + +STYLE_BOLD=$(tput bold) +STYLE_NORMAL=$(tput sgr0) + +WARNINGS=0 +ERRORS=0 + +# Logging function (with level and timestamps if `LOG_EXPANDED` is set to a truthy value) +log() { + # Allow the message to be piped for heredocs + local message="${2:-$(cat)}" + + if [[ "${LOG_EXPANDED:-0}" -ne 0 ]]; then + local level="$1" + local timestamp=$(date +"%Y-%m-%d %H:%M:%S") + + printf "[%s] [%s] %b\n" "${timestamp}" "${level}" "${message}" 1>&2 + else + printf "%b\n" "$message" + fi +} + +make_bold() { + # Allows heredoc expansion with pipes + local s="${1:-$(cat)}" + + printf "%s%s%s" "$STYLE_BOLD" "${s}" "$STYLE_NORMAL" +} + +# Print a blank line for visual separation. +horizontal_line() { + WIDTH=${COLUMNS:-$(tput cols)} + FILL_CHAR='-' + + # Prints a zero-length string but specifies it should be `$COLUMNS` wide, so the `printf` command pads it with blanks. + # We then use `tr` to replace those blanks with our padding character of choice. + printf '\n%*s\n\n' "$WIDTH" '' | tr ' ' "$FILL_CHAR" +} + +# munch args +POSITIONAL_ARGS=() + +SUBCOMMAND=install + +while [[ $# -gt 0 ]]; do + case $1 in + --ignore-warnings) + IGNORE_WARNINGS=y + ;; + -y | --yes) + YES=1 + ;; + --auto-update) + AUTO_UPDATE=y + ;; + --image-tag=*) + IMAGE_TAG="${1#"--image-tag="}" + ;; + --install) + SUBCOMMAND=install + ;; + --update) + SUBCOMMAND=update + ;; + -*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + ;; + esac + + shift +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +# Detect OS safely +detect_os() { + OS=$( + # shellcheck disable=SC1091 + source /etc/os-release >/dev/null 2>&1 + echo "${ID:-unknown}" + ) + if [[ "$OS" == "unknown" && "$(uname -s)" == "Darwin" ]]; then + OS="macos" + fi +} + +# Suggest package installation securely +suggest_install() { + local package=$1 + case "$OS" in + debian | ubuntu) echo " sudo apt update && sudo apt install -y $package" ;; + fedora) echo " sudo dnf install -y $package" ;; + arch) echo " sudo pacman -S --noconfirm $package" ;; + opensuse) echo " sudo zypper install -y $package" ;; + macos) echo " brew install $package" ;; + *) echo " Please install '$package' manually for your OS." ;; + esac +} + +# Resolve commands dynamically +NPROC_CMD=$(command -v nproc || echo "") +GREP_CMD=$(command -v grep || echo "") +DF_CMD=$(command -v df || echo "") + +# Check if a command exists +check_command() { + command -v "$1" >/dev/null 2>&1 +} + +# Platform Check +check_platform() { + PLATFORM_ARG='' + + local arch=$(uname -m) + + # Bash on MacOS doesn't support `@(pattern-list)` apparently? + if [[ "$arch" == "amd64" || "$arch" == "x86_64" ]]; then + log "INFO" "Platform Check: ${CHECKMARK} supported platform $arch" + elif [[ "$OS" == "macos" && "$arch" == arm64 ]]; then + # Ensure Apple Silicon runs the container as x86_64 using Rosetta + PLATFORM_ARG='--platform linux/amd64' + + log "WARNING" "Platform Check: ${WARNING} unsupported platform $arch" + log "INFO" <<-EOF + MacOS Apple Silicon is not currently supported, but the worker can still run through the Rosetta compatibility layer. + Performance and earnings will be less than a native node. + You may be prompted to install Rosetta when the worker node starts. + EOF + ((WARNINGS++)) + else + log "ERROR" "Platform Check: ${CROSSMARK} unsupported platform $arch" + log "INFO" "Join the Tashi Discord to request support for your system." + ((ERRORS++)) + return + fi +} + +# CPU Check +check_cpu() { + case "$OS" in + "macos") + threads=$(sysctl -n hw.ncpu) + ;; + *) + if [[ -z "$NPROC_CMD" ]]; then + log "WARNING" "'nproc' not found. Install coreutils:" + suggest_install "coreutils" + ((ERRORS++)) + return + fi + threads=$("$NPROC_CMD") + ;; + esac + + if [[ "$threads" -ge 4 ]]; then + log "INFO" "CPU Check: ${CHECKMARK} Found $threads threads (>= 4 recommended)" + elif [[ "$threads" -ge 2 ]]; then + log "WARNING" "CPU Check: ${WARNING} Found $threads threads (>= 2 required, 4 recommended)" + ((WARNINGS++)) + else + log "ERROR" "CPU Check: ${CROSSMARK} Only $threads threads found (Minimum: 2 required)" + ((ERRORS++)) + fi +} + +# Memory Check +check_memory() { + if [[ -z "$GREP_CMD" ]]; then + log "ERROR" "Memory Check: ${WARNING} 'grep' not found. Install grep:" + suggest_install "grep" + ((ERRORS++)) + return + fi + + case "$OS" in + "macos") + total_mem_bytes=$(sysctl -n hw.memsize) + total_mem_kb=$((total_mem_bytes / 1024)) + ;; + *) + total_mem_kb=$("$GREP_CMD" MemTotal /proc/meminfo | awk '{print $2}') + ;; + esac + + total_mem_gb=$((total_mem_kb / 1024 / 1024)) + + if [[ "$total_mem_gb" -ge 4 ]]; then + log "INFO" "Memory Check: ${CHECKMARK} Found ${total_mem_gb}GB RAM (>= 4GB recommended)" + elif [[ "$total_mem_gb" -ge 2 ]]; then + log "WARNING" "Memory Check: ${WARNING} Found ${total_mem_gb}GB RAM (>= 2GB required, 4GB recommended)" + ((WARNINGS++)) + else + log "ERROR" "Memory Check: ${CROSSMARK} Only ${total_mem_gb}GB RAM found (Minimum: 2GB required)" + ((ERRORS++)) + fi +} + +# Disk Space Check +check_disk() { + case "$OS" in + "macos") + available_disk_kb=$( + "$DF_CMD" -kcI 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + total_mem_bytes=$(sysctl -n hw.memsize) + ;; + *) + available_disk_kb=$( + "$DF_CMD" -kx tmpfs --total 2>/dev/null | + tail -1 | + awk '{print $4}' + ) + ;; + esac + + available_disk_gb=$((available_disk_kb / 1024 / 1024)) + + if [[ "$available_disk_gb" -ge 20 ]]; then + log "INFO" "Disk Space Check: ${CHECKMARK} Found ${available_disk_gb}GB free (>= 20GB required)" + else + log "ERROR" "Disk Space Check: ${CROSSMARK} Only ${available_disk_gb}GB free space (Minimum: 20GB required)" + ((ERRORS++)) + fi +} + +# Docker or Podman Check +check_container_runtime() { + # 首先检测操作系统 + detect_os + + if check_command "docker"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Docker is installed" + CONTAINER_RT=docker + + # 检查 Docker 是否运行 + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is running" + else + log "WARNING" "Docker Runtime Check: ${WARNING} Docker is installed but not running" + + # 根据操作系统启动 Docker + if [[ "$OS" == "macos" ]]; then + log "INFO" "Attempting to start Docker Desktop..." + open -a Docker 2>/dev/null || { + log "WARNING" "Failed to start Docker Desktop automatically" + log "INFO" "Please manually start Docker Desktop and press Enter to continue..." + read -r + } + + # 等待 Docker 启动 + log "INFO" "Waiting for Docker Desktop to start..." + local waited=0 + local max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + break + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if ! docker info >/dev/null 2>&1; then + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker failed to start after ${max_wait} seconds" + log "INFO" "Please ensure Docker Desktop is running and try again" + ((ERRORS++)) + fi + else + # Linux 系统尝试启动 Docker 服务 + if command -v systemctl >/dev/null 2>&1; then + log "INFO" "Attempting to start Docker service..." + if sudo systemctl start docker 2>/dev/null; then + sleep 3 + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker service failed to start" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Failed to start Docker service" + log "INFO" "Please manually start Docker service: sudo systemctl start docker" + ((ERRORS++)) + fi + else + log "ERROR" "Docker Runtime Check: ${CROSSMARK} Docker is not running and cannot be started automatically" + ((ERRORS++)) + fi + fi + fi + elif check_command "podman"; then + log "INFO" "Container Runtime Check: ${CHECKMARK} Podman is installed" + CONTAINER_RT=podman + else + log "WARNING" "Container Runtime Check: ${WARNING} Neither Docker nor Podman is installed." + + # 尝试安装 Docker + if [[ "$OS" == "macos" ]]; then + # 检查 Homebrew 是否安装 + if ! check_command "brew"; then + log "INFO" "Homebrew is not installed. Installing Homebrew first..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || { + log "ERROR" "Failed to install Homebrew" + ((ERRORS++)) + return + } + # 设置 Homebrew 环境 + if [[ -f "/opt/homebrew/bin/brew" ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -f "/usr/local/bin/brew" ]]; then + eval "$(/usr/local/bin/brew shellenv)" + fi + fi + + log "INFO" "Installing Docker Desktop via Homebrew..." + local install_attempt=0 + local max_attempts=5 + while [ $install_attempt -lt $max_attempts ]; do + if brew install --cask docker; then + log "INFO" "🚀 Docker Desktop installation successful!" + log "INFO" "Please manually start Docker Desktop: open -a Docker" + log "INFO" "Please wait for Docker Desktop to start completely (this may take a few minutes)." + read -p "Press Enter to continue (ensure Docker Desktop is running)..." + + # 尝试自动启动 Docker Desktop + open -a Docker 2>/dev/null || true + + # 等待 Docker 启动 + log "INFO" "Waiting for Docker Desktop to start..." + local waited=0 + local max_wait=60 + while [ $waited -lt $max_wait ]; do + if docker info >/dev/null 2>&1; then + log "INFO" "Docker Runtime Check: ${CHECKMARK} Docker is now running" + CONTAINER_RT=docker + return + fi + sleep 2 + waited=$((waited + 2)) + echo -n "." + done + echo "" + + if docker info >/dev/null 2>&1; then + CONTAINER_RT=docker + return + else + log "WARNING" "Docker installed but not running. Please start Docker Desktop manually." + ((ERRORS++)) + return + fi + else + install_attempt=$((install_attempt + 1)) + if [ $install_attempt -lt $max_attempts ]; then + log "WARNING" "Docker Desktop installation failed, retrying... ($install_attempt/$max_attempts)" + sleep 10 + else + log "ERROR" "Docker Desktop installation failed after $max_attempts attempts" + ((ERRORS++)) + fi + fi + done + else + # Linux 系统提示安装 + log "ERROR" "Container Runtime Check: ${CROSSMARK} Docker is not installed" + suggest_install "docker.io" + ((ERRORS++)) + fi + fi +} + +# Check network connectivity & NAT status +check_internet() { + # Step 1: Confirm Public Internet Access (No ICMP Required) + if curl -s --head --connect-timeout 3 https://google.com | grep "HTTP" >/dev/null 2>&1; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + elif wget --spider --timeout=3 --quiet https://google.com; then + log "INFO" "Internet Connectivity: ${CHECKMARK} Device has public Internet access." + else + log "ERROR" "Internet Connectivity: ${CROSSMARK} No internet access detected!" + ((ERRORS++)) + fi +} + +get_local_ip() { + if [[ "$OS" == "macos" ]]; then + LOCAL_IP=$(ifconfig -l | xargs -n1 ipconfig getifaddr) + elif check_command hostname; then + LOCAL_IP=$(hostname -I | awk '{print $1}') + elif check_command ip; then + # Use `ip route` to find what IP address connects to the internet + LOCAL_IP=$(ip route get '1.0.0.0' | grep -Po "src \K(\S+)") + fi +} + +get_public_ip() { + PUBLIC_IP=$(curl -s https://api.ipify.org || wget -qO- https://api.ipify.org) +} + +check_nat() { + local nat_message=$( + cat <<-EOF + If this device is not accessible from the Internet, some DePIN services will be disabled; + earnings may be less than a publicly accessible node. + + For maximum earning potential, ensure UDP port $AGENT_PORT is forwarded to this device. + Consult your router’s manual or contact your Internet Service Provider for details. + EOF + ); + + # Step 2: Get local & public IP + get_local_ip + get_public_ip + + if [[ -z "$LOCAL_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine local IP." + log "WARNING" "$nat_message" + return + fi + + if [[ -z "$PUBLIC_IP" ]]; then + log "WARNING" "NAT Check: ${WARNING} Could not determine public IP." + log "WARNING" "$nat_message" + return + fi + + # Step 3: Determine NAT Type + if [[ "$LOCAL_IP" == "$PUBLIC_IP" ]]; then + log "INFO" "NAT Check: ${CHECKMARK} Open NAT / Publicly accessible (Public IP: $PUBLIC_IP)" + return + fi + + log "WARNING" "NAT Check: NAT detected (Local: $LOCAL_IP, Public: $PUBLIC_IP)" + log "WARNING" "$nat_message" +} + +check_root_required() { + # Docker and Podman on Mac run a Linux VM. The client commands outside the VM do not require root. + if [[ "$OS" == "macos" ]]; then + SUDO_CMD='' + log "INFO" "Privilege Check: ${CHECKMARK} Root privileges are not needed on MacOS" + return + fi + + if [[ "$CONTAINER_RT" == "docker" ]]; then + if (groups "$USER" | grep docker >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User is in 'docker' group." + log "INFO" "Worker container can be started without needing superuser privileges." + elif [[ -w "$DOCKER_HOST" ]] || [[ -w "/var/run/docker.sock" ]]; then + log "INFO" "Privilege Check: ${CHECKMARK} User has access to the Docker daemon socket." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo -g docker" + log "WARNING" "Privilege Check: ${WARNING} User is not in 'docker' group." + log "WARNING" <<-EOF + ${WARNING} 'docker run' command will be executed using '${SUDO_CMD}' + You may be prompted for your password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $DOCKER_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + elif [[ "$CONTAINER_RT" == "podman" ]]; then + # Check that the user and their login group are assigned substitute ID ranges + if (grep "^$USER:" /etc/subuid >/dev/null) && (grep "^$(id -gn):" /etc/subgid >/dev/null); then + log "INFO" "Privilege Check: ${CHECKMARK} User can create Podman containers without root." + log "INFO" "Worker container can be started without needing superuser privileges." + else + SUDO_CMD="sudo" + log "WARNING" "Privilege Check: ${WARNING} User cannot create rootless Podman containers." + log "WARNING" <<-EOF + ${WARNING} 'podman run' command will be executed using '${SUDO_CMD}' + You may be prompted for your sudo password during setup. + + Rootless configuration is recommended to avoid this requirement. + For more information, see $PODMAN_ROOTLESS_LINK + EOF + ((WARNINGS++)) + fi + fi +} + +prompt_auto_updates() { + log "INFO" <<-EOF + Your DePIN worker will require periodic updates to ensure that it keeps up with new features and bug fixes. + Out-of-date workers may be excluded from the DePIN network and be unable to complete jobs or earn rewards. + + We recommend enabling automatic updates, which take place entirely in the container + and do not make any changes to your system. + + Otherwise, you will need to check the worker logs regularly to see when a new update is available, + and apply the update manually.\n + EOF + + # 默认启用自动更新(自动选择 Y) + log "INFO" "Automatic updates enabled (default: yes)." + AUTO_UPDATE=y + + # Blank line + echo "" +} + +prompt() { + local prompt="${1?}" + local variable="${2?}" + + # read -p in zsh is "read from coprocess", whatever that means + printf "%b" "$prompt" + + # Always read from TTY even if piped in + read -r "${variable?}" /dev/null 2>&1; then + return 1 + fi + + # 使用 python3 解密(直接传递变量) + python3 -c " +import base64 +import sys + +encrypted = '$encrypted' +key = 'RL_SWARM_2024' + +try: + decoded = base64.b64decode(encrypted) + result = bytearray() + key_bytes = key.encode('utf-8') + for i, byte in enumerate(decoded): + result.append(byte ^ key_bytes[i % len(key_bytes)]) + print(result.decode('utf-8')) +except Exception as e: + sys.exit(1) +" 2>/dev/null +} + +# 获取设备唯一标识符(完全照搬 upload_devices.sh 的 get_mac_serial 函数) +get_device_code() { + local serial="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + # ===== macOS: Use hardware serial number ===== + # Method 1: Use system_profiler (recommended, most reliable) + if command -v system_profiler >/dev/null 2>&1; then + serial=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + + # Method 2: If method 1 fails, use ioreg + if [ -z "$serial" ]; then + if command -v ioreg >/dev/null 2>&1; then + serial=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + fi + + # Method 3: If both methods fail, try sysctl + if [ -z "$serial" ]; then + if command -v sysctl >/dev/null 2>&1; then + serial=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + fi + else + # ===== Linux: Use machine-id / hardware UUID ===== + # Prefer /etc/machine-id (system unique identifier) + if [ -f /etc/machine-id ]; then + serial=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + + # Second try DMI hardware UUID + if [ -z "$serial" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + serial=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + + # Third try hostnamectl machine ID + if [ -z "$serial" ] && command -v hostnamectl >/dev/null 2>&1; then + serial=$(hostnamectl 2>/dev/null | grep "Machine ID" | awk -F': ' '{print $2}' | xargs) + fi + fi + + echo "$serial" +} + +# 获取当前用户名(完全照搬 upload_devices.sh 的 get_current_user 函数) +get_current_user() { + local user="" + + # Prefer $USER environment variable + if [ -n "$USER" ]; then + user="$USER" + # Second use whoami + elif command -v whoami >/dev/null 2>&1; then + user=$(whoami) + # Last try id command + elif command -v id >/dev/null 2>&1; then + user=$(id -un) + fi + + echo "$user" +} + +# 构建 JSON(完全照搬 upload_devices.sh 的 build_json 函数) +build_json() { + local customer_name="$1" + local device_code="$2" + + echo "[{\"customer_name\":\"$customer_name\",\"device_code\":\"$device_code\"}]" +} + +# 获取服务器配置(支持加密配置,参考 upload_devices.sh) +get_server_config() { + # 加密的默认配置(与 upload_devices.sh 保持一致) + local ENCRYPTED_SERVER_URL="OjgrI21ufX9vCx4DAGRibmJhb2N8bAgIAgxh" + local ENCRYPTED_API_KEY="EyUFNC8XNgJwAWNLdzo5BgJjMQoHbXBDAQ0hCyoUA3E2ODtRUVleYjxtCmo=" + + # 优先级:环境变量 > 加密默认值 + if [ -n "$TASHI_SERVER_URL" ]; then + SERVER_URL="$TASHI_SERVER_URL" + log "INFO" "Using SERVER_URL from TASHI_SERVER_URL environment variable" + elif [ -n "$SERVER_URL" ]; then + # 使用 SERVER_URL 环境变量 + log "INFO" "Using SERVER_URL from SERVER_URL environment variable" + : + else + # 使用加密的默认值并解密 + log "INFO" "Decrypting SERVER_URL from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default SERVER_URL" + SERVER_URL="" + else + # 使用 decrypt_string 函数(更可靠) + SERVER_URL=$(decrypt_string "$ENCRYPTED_SERVER_URL" 2>/dev/null || echo "") + fi + fi + + if [ -n "$TASHI_API_KEY" ]; then + API_KEY="$TASHI_API_KEY" + log "INFO" "Using API_KEY from TASHI_API_KEY environment variable" + elif [ -n "$API_KEY" ]; then + # 使用 API_KEY 环境变量 + log "INFO" "Using API_KEY from API_KEY environment variable" + : + else + # 使用加密的默认值并解密 + log "INFO" "Decrypting API_KEY from encrypted default..." + if ! command -v python3 >/dev/null 2>&1; then + log "WARNING" "python3 not found, cannot decrypt default API_KEY" + API_KEY="" + else + # 使用 decrypt_string 函数(更可靠) + API_KEY=$(decrypt_string "$ENCRYPTED_API_KEY" 2>/dev/null || echo "") + fi + fi + + # 导出为全局变量供其他函数使用 + export SERVER_URL API_KEY + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + log "INFO" "Server configuration not available, device check will be skipped" + fi +} + +# 检查设备状态(完全照搬 upload_devices.sh 的 check_device_status 函数) +# Return value semantics (server convention): +# 1 -> Enabled (normal), function returns 0, script continues +# 0 -> Disabled/not found: return 2 (for caller to identify) +# Other/network error -> return 1 (treated as exception) +check_device_status() { + local device_code="$1" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + # 未配置服务器信息,跳过检查 + return 0 + fi + + # 完全照搬 upload_devices.sh 的实现(不使用超时,与原始脚本保持一致) + local status + status=$(curl -s "${SERVER_URL}/api/public/device/status?device_code=${device_code}") + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + # Network error or abnormal return value + # 在安装脚本中,网络错误也返回 1,让调用者决定如何处理 + return 1 + fi +} + +# 上传设备信息(完全照搬 upload_devices.sh 的逻辑,不使用超时) +upload_device_info() { + local device_code="$1" + local customer_name="$2" + + # 获取服务器配置 + get_server_config + + if [ -z "$SERVER_URL" ] || [ -z "$API_KEY" ]; then + return 1 + fi + + # Build JSON(完全照搬 upload_devices.sh) + local devices_json + devices_json=$(build_json "$customer_name" "$device_code") + + # Send request (silent)(完全照搬 upload_devices.sh,不使用超时) + local response + response=$(curl -s -X POST "$SERVER_URL/api/public/customer-devices/batch" \ + -H "Content-Type: application/json" \ + -d "{ + \"api_key\": \"$API_KEY\", + \"devices\": $devices_json + }") + + # Check if upload is successful (based on response body) + # Support multiple success indicators(完全照搬 upload_devices.sh): + # 1. code: \"0000\" + # 2. success_count > 0 + # 3. Traditional success:true or status:\"success\" or code:200 + if echo "$response" | grep -qE '"code"\s*:\s*"0000"|"success_count"\s*:\s*[1-9]|"success"\s*:\s*true|"status"\s*:\s*"success"|"code"\s*:\s*200'; then + return 0 + else + return 1 + fi +} + +# ============ 修改点:完全跳过设备检查 ============ +# 直接覆盖原函数,让它直接返回成功,不执行任何验证 +setup_device_check() { + log "INFO" "Device check bypassed (modified script)" + return 0 +} + +check_and_stop_existing_container() { + # 检查容器是否存在(运行中或已停止) + if ${CONTAINER_RT} ps -a --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Found existing container: ${CONTAINER_NAME}" + + # 检查容器是否在运行 + if ${CONTAINER_RT} ps --format "{{.Names}}" 2>/dev/null | grep -q "^${CONTAINER_NAME}$"; then + log "INFO" "Stopping running container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} stop "$CONTAINER_NAME" >/dev/null 2>&1 + fi + + # 删除容器(无论是否运行) + log "INFO" "Removing existing container..." + ${SUDO_CMD:+"$SUDO_CMD "}${CONTAINER_RT} rm "$CONTAINER_NAME" >/dev/null 2>&1 + + log "INFO" "Existing container removed: ${CHECKMARK}" + fi +} + +install() { + # 设备检测已在脚本开始时完成,这里直接继续安装流程 + # 检查并停止已存在的容器 + check_and_stop_existing_container + + log "INFO" "Installing worker. The commands being run will be printed for transparency.\n" + + log "INFO" "Starting worker in interactive setup mode.\n" + + local setup_cmd=$(make_setup_cmd) + + sh -c "set -ex; $setup_cmd" + + local exit_code=$? + + echo "" + + if [[ $exit_code -eq 130 ]]; then + log "INFO" "Worker setup cancelled. You may re-run this script at any time." + exit 0 + elif [[ $exit_code -ne 0 ]]; then + log "ERROR" "Setup failed ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + exit 1 + fi + + local run_cmd=$(make_run_cmd) + + sh -c "set -ex; $run_cmd" + + exit_code=$? + + echo "" + + if [[ $exit_code -ne 0 ]]; then + log "ERROR" "Worker failed to start ($exit_code): ${CROSSMARK} Please see the following page for troubleshooting instructions: ${TROUBLESHOOT_LINK}." + + # 检查是否是授权文件缺失的问题 + local logs_output=$(docker logs "$CONTAINER_NAME" 2>&1 | tail -5) + if echo "$logs_output" | grep -q "node_auth.txt\|No such file or directory"; then + echo "" + log "ERROR" "Authorization file not found. This usually means:" + log "ERROR" " 1. The interactive setup was not completed" + log "ERROR" " 2. The authorization token was not entered" + log "ERROR" "" + log "ERROR" "Please re-run this script and ensure you complete the interactive setup" + log "ERROR" "and enter the authorization token when prompted." + fi + fi +} + +update() { + log "INFO" "Updating worker. The commands being run will be printed for transparency.\n" + + local container_old="$CONTAINER_NAME" + local container_new="$CONTAINER_NAME-new" + + local create_cmd=$(make_run_cmd "" "create" "$container_new" "$container_old") + + # Execute this whole next block as `sudo` if necessary. + # Piping means the sub-process reads line by line and can tell us right where it failed. + # Note: when referring to local shell variables *in* the script, be sure to escape: \$foo + ${SUDO_CMD+"$SUDO_CMD "}bash <<-EOF + set -x + + ($CONTAINER_RT inspect "$CONTAINER_NAME-old" >/dev/null 2>&1) + + if [ \$? -eq 0 ]; then + echo "$CONTAINER_NAME-old already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi + + ($CONTAINER_RT inspect "$container_new" >/dev/null 2>&1) + + if [ \$? -eq 0 ]; then + echo "$container_new already exists (presumably from a failed run), please delete it before continuing" 1>&2 + exit 1 + fi + + set -ex + + $create_cmd + $CONTAINER_RT stop $container_old + $CONTAINER_RT start $container_new + $CONTAINER_RT rename $container_old $CONTAINER_NAME-old + $CONTAINER_RT rename $container_new $CONTAINER_NAME + + echo -n "Would you like to delete $CONTAINER_NAME-old? (Y/n) " + read -r choice &2 <<-EOF + + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #-:::::::::::::::::::::::::::::=%@@@@@@@@@@@@@@%=:::::::::::::::::::::::::::::-# + @@*::::::::::::::::::::::::::::::+%@@@@@@@@@@%+::::::::::::::::::::::::::::::*@@ + @@@@+::::::::::::::::::::::::::::::+%@@@@@@%+::::::::::::::::::::::::::::::+@@@@ + @@@@@%=::::::::::::::::::::::::::::::+%@@%+::::::::::::::::::::::::::::::=%@@@@@ + @@@@@@@#-::::::::::::::::::::::::::::::@@::::::::::::::::::::::::::::::-#@@@@@@@ + @@@@@@@@@*:::::::::::::::::::::::::::::@@:::::::::::::::::::::::::::::*@@@@@@@@@ + @@@@@@@@@@%+:::::::::::::::::::::::::::@@:::::::::::::::::::::::::::+%@@@@@@@@@@ + @@@@@@@@@@@@%++++++++++++-:::::::::::::@@:::::::::::::-++++++++++++%@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@#-:::::::::::@@:::::::::::-#@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@*::::::::::@@::::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@*:::::::::@@:::::::::*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@#:::::::::@@:::::::::#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:::::::@@:::::::+%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::::@@::::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*-::@@::-*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#=@@=#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + + + EOF +} + +setup_monitor_script() { + # 优先使用用户目录,避免权限问题 + local monitor_script="$HOME/.local/bin/monitor_tashi.sh" + local log_file="/tmp/tashi_monitor.log" + + # 确保用户目录存在 + mkdir -p "$HOME/.local/bin" 2>/dev/null || true + + # 如果用户目录创建失败,尝试系统目录(需要 sudo) + if [[ ! -d "$HOME/.local/bin" ]] || [[ ! -w "$HOME/.local/bin" ]]; then + monitor_script="/usr/local/bin/monitor_tashi.sh" + fi + + # 创建监控脚本 + if [[ "$monitor_script" == "/usr/local/bin/monitor_tashi.sh" ]]; then + # 需要 sudo 权限 + ${SUDO_CMD:+"$SUDO_CMD "}bash -c "cat > '$monitor_script'" << 'MONITOR_EOF' +#!/bin/bash +CONTAINER_NAME="tashi-depin-worker" +LOG_FILE="/tmp/tashi_monitor.log" + +# 检查容器是否存在 +if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查容器是否在运行 +if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查最近 5 分钟是否有断开连接 +if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then + # 检查是否在最近 2 分钟内已经重连成功 + if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then + echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null + docker restart "$CONTAINER_NAME" >/dev/null 2>&1 + fi +fi +MONITOR_EOF + ${SUDO_CMD:+"$SUDO_CMD "}chmod +x "$monitor_script" 2>/dev/null || true + else + # 用户目录,不需要 sudo + cat > "$monitor_script" << 'MONITOR_EOF' +#!/bin/bash +CONTAINER_NAME="tashi-depin-worker" +LOG_FILE="/tmp/tashi_monitor.log" + +# 检查容器是否存在 +if ! docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查容器是否在运行 +if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then + exit 0 +fi + +# 检查最近 5 分钟是否有断开连接 +if docker logs --since 5m "$CONTAINER_NAME" 2>&1 | grep -q "disconnected from orchestrator"; then + # 检查是否在最近 2 分钟内已经重连成功 + if ! docker logs --since 2m "$CONTAINER_NAME" 2>&1 | grep -q "resource node successfully bonded"; then + echo "$(date '+%Y-%m-%d %H:%M:%S'): Restarting container due to disconnection" >> "$LOG_FILE" 2>/dev/null + docker restart "$CONTAINER_NAME" >/dev/null 2>&1 + fi +fi +MONITOR_EOF + chmod +x "$monitor_script" 2>/dev/null || true + fi + + # 验证脚本是否创建成功 + if [[ ! -f "$monitor_script" ]]; then + log "WARN" "Failed to create monitor script at $monitor_script" + return 1 + fi + + # 添加到 crontab(每 5 分钟检查一次) + local cron_entry="*/5 * * * * $monitor_script >/dev/null 2>&1" + + # 检查是否已存在,如果存在但路径不同,先删除旧的 + local existing_cron=$(crontab -l 2>/dev/null | grep "monitor_tashi.sh" || true) + if [[ -n "$existing_cron" ]] && [[ "$existing_cron" != *"$monitor_script"* ]]; then + # 删除旧的 crontab 条目 + crontab -l 2>/dev/null | grep -v "monitor_tashi.sh" | crontab - 2>/dev/null || true + fi + + # 如果不存在,添加新的 + if ! crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + (crontab -l 2>/dev/null; echo "$cron_entry") | crontab - 2>/dev/null || true + fi + + # 验证 crontab 是否添加成功 + if crontab -l 2>/dev/null | grep -q "monitor_tashi.sh"; then + return 0 + else + log "WARN" "Failed to add monitor script to crontab" + return 1 + fi +} + +post_install() { + echo "" + + log "INFO" "Worker is running: ${CHECKMARK}" + + echo "" + + local status_cmd="${SUDO_CMD:+"$sudo "}${CONTAINER_RT} ps" + local logs_cmd="${sudo:+"$sudo "}${CONTAINER_RT} logs $CONTAINER_NAME" + + log "INFO" "To check the status of your worker: '$status_cmd' (name: $CONTAINER_NAME)" + log "INFO" "To view the logs of your worker: '$logs_cmd'" + + # 设置监控脚本 + setup_monitor_script + + # 创建桌面快捷方式 + create_desktop_shortcut +} + +create_desktop_shortcut() { + local desktop_path="" + + # 检测桌面路径 + if [[ -n "$HOME" ]]; then + # macOS + if [[ "$OS" == "macos" ]]; then + desktop_path="$HOME/Desktop" + # Linux - 尝试常见的桌面路径 + elif [[ -d "$HOME/Desktop" ]]; then + desktop_path="$HOME/Desktop" + elif [[ -d "$HOME/桌面" ]]; then + desktop_path="$HOME/桌面" + fi + fi + + if [[ -z "$desktop_path" || ! -d "$desktop_path" ]]; then + log "INFO" "Desktop directory not found, skipping shortcut creation." + return + fi + + local shortcut_file="$desktop_path/Tashi.command" + + # 创建快捷方式文件 + cat > "$shortcut_file" <<'SCRIPT_EOF' +#!/bin/bash + +# Tashi DePIN Worker restart script + +# 设置颜色 +GREEN="\033[32m" +RED="\033[31m" +YELLOW="\033[33m" +RESET="\033[0m" + +# 配置 +CONTAINER_NAME="tashi-depin-worker" +AUTH_VOLUME="tashi-depin-worker-auth" +AUTH_DIR="/home/worker/auth" +AGENT_PORT=39065 +IMAGE_TAG="ghcr.io/tashigg/tashi-depin-worker:0" +PLATFORM_ARG="--platform linux/amd64" +RUST_LOG="info,tashi_depin_worker=debug,tashi_depin_common=debug" + +# ============ 设备检测函数 ============ +# 获取设备唯一标识 +get_device_code() { + local device_code="" + + if [[ "$OSTYPE" == "darwin"* ]]; then + if command -v system_profiler >/dev/null 2>&1; then + device_code=$(system_profiler SPHardwareDataType 2>/dev/null | grep "Serial Number" | awk -F': ' '{print $2}' | xargs) + fi + if [ -z "$device_code" ] && command -v ioreg >/dev/null 2>&1; then + device_code=$(ioreg -l | grep IOPlatformSerialNumber 2>/dev/null | awk -F'"' '{print $4}') + fi + if [ -z "$device_code" ] && command -v sysctl >/dev/null 2>&1; then + device_code=$(sysctl -n hw.serialnumber 2>/dev/null) + fi + else + if [ -f /etc/machine-id ]; then + device_code=$(cat /etc/machine-id 2>/dev/null | xargs) + fi + if [ -z "$device_code" ] && [ -f /sys/class/dmi/id/product_uuid ]; then + device_code=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null | xargs) + fi + fi + + echo "$device_code" +} + +# 检查设备状态 +check_device_status() { + local device_code="$1" + local server_url="${TASHI_SERVER_URL:-}" + local api_key="${TASHI_API_KEY:-}" + + if [ -z "$server_url" ] || [ -z "$api_key" ]; then + # 尝试使用外部脚本 + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + # 使用外部脚本检查(静默模式) + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + return 2 # 设备被禁用 + else + return 0 # 网络错误,允许继续 + fi + fi + else + # 未配置,允许继续 + return 0 + fi + fi + + local status + status=$(curl -s "${server_url}/api/public/device/status?device_code=${device_code}" 2>/dev/null) + + if [ "$status" = "1" ]; then + return 0 + elif [ "$status" = "0" ]; then + return 2 + else + return 0 # 网络错误,允许继续 + fi +} + +perform_device_check() { + local upload_script="" + if [ -f "./upload_devices.sh" ] && [ -x "./upload_devices.sh" ]; then + upload_script="./upload_devices.sh" + elif [ -f "$HOME/rl-swarm/upload_devices.sh" ] && [ -x "$HOME/rl-swarm/upload_devices.sh" ]; then + upload_script="$HOME/rl-swarm/upload_devices.sh" + fi + + if [ -n "$upload_script" ]; then + if CHECK_ONLY=true "$upload_script" >/dev/null 2>&1; then + return 0 + else + local rc=$? + if [ "$rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi + fi + + local device_code=$(get_device_code) + if [ -z "$device_code" ]; then + return 0 + fi + + if check_device_status "$device_code"; then + return 0 + else + local status_rc=$? + if [ "$status_rc" -eq 2 ]; then + exit 2 + else + return 0 + fi + fi +} + +# 切换到脚本所在目录 +cd "$(dirname "$0")" || exit 1 + +# 清屏 +clear + +perform_device_check >/dev/null 2>&1 + +if docker stop "$CONTAINER_NAME" >/dev/null 2>&1; then + docker rm "$CONTAINER_NAME" >/dev/null 2>&1 +fi + +if docker run -d \ + -p "$AGENT_PORT:$AGENT_PORT" \ + -p 127.0.0.1:9000:9000 \ + --mount type=volume,src="$AUTH_VOLUME",dst="$AUTH_DIR" \ + --name "$CONTAINER_NAME" \ + -e RUST_LOG="$RUST_LOG" \ + --health-cmd='pgrep -f tashi-depin-worker || exit 1' \ + --health-interval=30s \ + --health-timeout=10s \ + --health-retries=3 \ + --restart=unless-stopped \ + --pull=always \ + $PLATFORM_ARG \ + "$IMAGE_TAG" \ + run "$AUTH_DIR" \ + --unstable-update-download-path /tmp/tashi-depin-worker; then + : +else + exit 1 +fi + +docker logs -f "$CONTAINER_NAME" +SCRIPT_EOF + + # 设置执行权限 + chmod +x "$shortcut_file" + + log "INFO" "Desktop shortcut created: $shortcut_file" +} + +# Detect OS before running checks +detect_os + +# ============ 修改点:直接跳过设备检查 ============ +# 直接注释掉原来的设备检查代码,不执行任何设备验证 +log "INFO" "Device registration check has been bypassed (modified script version)" +log "INFO" "Continuing with Docker check..." + +# Check Docker (required for installation) +# This must be done before any other checks since Docker is essential +log "INFO" "Checking Docker installation and runtime..." +check_container_runtime + +# Run all checks +display_logo + +log "INFO" "Starting system checks..." + +echo "" + +check_platform +check_cpu +check_memory +check_disk +check_root_required +check_internet + +echo "" + +check_warnings + +horizontal_line + +# Integrated NAT check. This is separate from system requirements because most manually started worker nodes +# are expected to be behind some sort of NAT, so this is mostly informational. +check_nat + +horizontal_line + +prompt_auto_updates + +horizontal_line + +prompt_continue + +case "$SUBCOMMAND" in + install) install ;; + update) update ;; + *) + log "ERROR" "BUG: no handler for $($SUBCOMMAND)" + exit 1 +esac + +post_install From 1c3a407f72090fc38203636a62bc171cfc916ffc Mon Sep 17 00:00:00 2001 From: moxiang555555 Date: Sat, 7 Mar 2026 14:16:09 +0800 Subject: [PATCH 14/14] Update deploy_wai_for_mac.sh --- WAI/deploy_wai_for_mac.sh | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/WAI/deploy_wai_for_mac.sh b/WAI/deploy_wai_for_mac.sh index 7a280b4..af25382 100644 --- a/WAI/deploy_wai_for_mac.sh +++ b/WAI/deploy_wai_for_mac.sh @@ -80,17 +80,36 @@ cleanup_wombo() { install_wai_cli() { if ! command -v wai >/dev/null 2>&1; then log "安装 WAI CLI..." - # 先安装 bash + + # ----- 修改部分:安装新版 bash 并使用它执行安装脚本 ----- if [[ "$OS_TYPE" == "macos" ]]; then - log "安装 bash..." - brew install bash || error "bash 安装失败" + # 检查是否已经安装了较新版本的 bash(通过 brew) + if ! command -v "$(brew --prefix)/bin/bash" >/dev/null 2>&1; then + log "安装新版 bash..." + brew install bash || error "bash 安装失败" + else + log "新版 bash 已安装" + fi + # 获取 brew bash 的路径 + BREW_BASH="$(brew --prefix)/bin/bash" + if [[ ! -x "$BREW_BASH" ]]; then + error "无法找到 brew 安装的 bash" + fi + log "使用 brew bash: $BREW_BASH" + else + # Linux 下默认系统 bash 通常较新,直接使用系统 bash + BREW_BASH="bash" fi - curl -fsSL https://app.w.ai/install.sh | bash || error "WAI CLI 安装失败" + + # 使用指定的 bash 执行安装脚本 + curl -fsSL https://app.w.ai/install.sh | "$BREW_BASH" || error "WAI CLI 安装失败" + # ---------------------------------------------------- + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc export PATH="$HOME/.local/bin:$PATH" - source ~/.zshrc || true - source ~/.bashrc || true + source ~/.zshrc 2>/dev/null || true + source ~/.bashrc 2>/dev/null || true log "WAI CLI 安装成功" else log "WAI CLI 已安装,版本:$(wai --version)" @@ -152,8 +171,6 @@ configure_env() { fi } - - run_wai_worker() { RETRY=1 log "开始运行 WAI Worker..." @@ -198,4 +215,4 @@ main() { run_wai_worker } -main \ No newline at end of file +main