Skip to content

创建自定义项目 ​

如果你希望由 Codex 协助收集项目资料并自动创建项目、矿池和运行脚本,请先阅读使用 MCP 创建自定义项目。本文主要介绍手工创建时的脚本修改与验收要求。

创建自定义项目时,建议复制一份已经可用的项目脚本,只修改本项目相关的配置。

重点

只修改“项目配置、项目启动命令、算力解析”三部分。不要修改脚本后半部分的 BLSFlow 通用切换流程。

创建项目 ​

进入 BLSFlow 控制台的“项目”页面,点击“新建自定义项目”,填写项目名称、项目符号、算法和支持系统。

添加矿池后,至少定义一个钱包参数:

字段建议值
参数名称钱包地址
显示名称wallet
类型string
是否必填必填

脚本中可以使用:

  • wallet:钱包地址占位符,在执行时平台自动替换配置的值。

脚本必须修改的地方 ​

1. 项目符号 ​

把 coin 改为新项目的符号。应与控制台中填写的项目符号一致,建议仅使用小写字母、数字和短横线。

bash
readonly coin="xmr"

service_name、work_dir、launch_script_path 和 hashrate_script_path 会根据 coin 自动生成,不要分别写死成其他项目。

2. 项目软件 ​

修改项目可执行文件名:

bash
readonly miner_bin="${install_dir}/SRBMiner-MULTI"

同时修改安装脚本的软件名:

bash
curl -sSfL "$remote_shell_url/install_software.sh" | bash -s -- srbminer-multi

这两处必须匹配。切换前 BLSFlow 会检查 miner_bin 是否存在且可执行。

3. 项目启动命令 ​

在 generate_start_script 中修改 exec 命令,使用新项目的算法和项目参数:

bash
exec ${miner_bin} \
  --algorithm neuromorph \
  --pool ${pool} \
  --wallet ${wallet} \
  --worker "\${worker}" \
  --cpu-threads -1 \
  --password x \
  --disable-gpu

需要修改的通常是:

  • 算法名称。
  • 矿池、钱包和 worker 的参数格式。
  • CPU 线程数、密码及其他项目专用参数。

不要写死用户配置

不要在 exec 命令中写死矿池或钱包地址。应保留 ${pool} 和 ${wallet},它们分别来自 pool_url 和 wallet。

4. 算力日志解析 ​

不同项目的日志格式不同。需要在 generate_hashrate_script 中修改:

bash
readonly match_pattern='CPU[[:space:]]*:|Avg\. 1 min\.'

以及后面从日志中提取 value 和 unit 的 awk 规则。

hashrate.sh 的最终输出必须满足:

  • stdout 只输出一个数值。
  • 单位统一为 kH/s。
  • 服务未运行或暂无算力时输出 0。

通常不需要修改的地方 ​

如果新项目的钱包、矿池配置方式与示例相同,保留以下代码:

bash
main() {
  local pool="{{pool_url}}"
  local wallet="{{wallet}}"

  # 其余流程保持不变
}

只有当项目确实需要额外参数时,才在控制台新增参数定义,并在这里读取对应的 。

不能修改的地方 ​

以下代码是 BLSFlow 通用切换契约,不要删除、改名或改变执行顺序。

不要改动钩子函数名 ​

bash
blsflow_coin_ensure_installed() {
  return 0
}

blsflow_coin_miner_executable() {
  printf '%s\n' "$miner_bin"
}

blsflow_coin_miner_executable 必须输出项目可执行文件的绝对路径。

不要删除环境变量 ​

bash
export COIN="$coin"
export SERVICE_NAME="$service_name"
export MINER_CONFIG_FINGERPRINT="$fingerprint"
export INSTALL_DIR="$install_dir"
export WORK_DIR="$work_dir"
export LAUNCH_SCRIPT_PATH="$launch_script_path"

这些变量用于创建 systemd 服务、判断配置是否变更以及定位运行文件。

不要把 source 改成子进程执行 ​

bash
set +u
# shellcheck disable=SC1090
source <(curl -sSfL "${remote_shell_url%/}/miner-switch.sh")
blsflow_miner_switch_main
set -u

必须在当前 shell 中 source 远程脚本,否则通用切换器无法使用项目脚本中的钩子函数。

不要修改通用切换器 ​

miner-switch.sh 负责:

  • 使用锁防止同时切换。
  • 根据配置指纹更新 systemd 服务。
  • 停止并禁用其他项目服务,保证单机只运行一个项目。
  • 启动目标服务,并在切换失败时尽力恢复原状态。

自定义项目脚本只需调用它,不应复制或重写其内部逻辑。

保存前检查 ​

保存并启用项目前,逐项确认:

  • [ ] coin 与项目符号一致。
  • [ ] 安装软件名与 miner_bin 一致。
  • [ ] exec 使用正确的算法和项目参数。
  • [ ] 矿池和钱包没有写死,仍使用 ${pool} 和 ${wallet}。
  • [ ] hashrate.sh 能解析当前项目的日志,并输出 kH/s。
  • [ ] 通用钩子、环境变量、source 和 blsflow_miner_switch_main 未被改动。

先在一台测试设备上切换。任务显示“切币完成”后,检查:

bash
systemctl status blsflow-miner-xmr.service --no-pager
journalctl -u blsflow-miner-xmr.service -n 100 --no-pager
bash /opt/blsflow/run/xmr/hashrate.sh

确认目标服务正常运行、矿池和钱包正确、算力能正常输出后,再用于其他设备。

完整示例 ​

shell
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# !!!币种!!!
readonly coin="xmr"

readonly service_name="blsflow-miner-${coin}.service"
readonly install_dir="/opt/blsflow/miner"
readonly work_dir="/opt/blsflow/run/${coin}"
readonly launch_script_path="${work_dir}/start.sh"
readonly hashrate_script_path="${work_dir}/hashrate.sh"
readonly miner_bin="${install_dir}/SRBMiner-MULTI"

# 定义远程shell地址
readonly remote_shell_url="http://script.blsflow.com/miner/common"

# !!!安装项目软件!!!
install_software() {
  command -v curl >/dev/null 2>&1 || {
    echo "缺少依赖:curl" >&2
    return 127
  }

  curl -sSfL "$remote_shell_url/install_software.sh" | bash -s -- srbminer-multi
}

# !!!启动脚本!!!
generate_start_script() {
  local pool="${1:?}" wallet="${2:?}"
  mkdir -p "$(dirname "$launch_script_path")" "$work_dir"
  cat >"$launch_script_path" <<EOF
#!/usr/bin/env bash
set -euo pipefail

# 生成项目名称
generate_worker() {
    local ip="unknown"
    ip="\$(hostname -I 2>/dev/null | awk '{print \$1}' || true)"
    [[ -n "\$ip" ]] || ip="unknown"

    if [[ "\$ip" != "unknown" ]]; then
      local ip_dash="\${ip//./-}"
      echo "auto-\${ip_dash}"
    else
      echo "auto-\$(date +%s)"
    fi
}

worker="\$(generate_worker)"

exec ${miner_bin} -a randomx -o ${pool} -u ${wallet}.\${worker}
EOF
  chmod 0755 "$launch_script_path"
}

# !!!输出算力!!!
generate_hashrate_script() {
  mkdir -p "$(dirname "$hashrate_script_path")"
  cat >"$hashrate_script_path" <<EOF
#!/usr/bin/env bash
set -euo pipefail

readonly service_name="${service_name}"
readonly match_pattern='CPU[[:space:]]*:|Avg\. 1 min\.'

# 服务未在运行时不读 journal 历史算力行,避免停止后仍返回旧算力(幻读)
if ! systemctl is-active --quiet "\${service_name}" 2>/dev/null; then
  echo 0
  exit 0
fi

# 优先从最近日志中抓取一条算力日志;若没有再短暂跟随(避免脚本一直卡住)。
# 支持两种格式:
# - CPU  : 3381.54 H/s ...
# - Avg. 1 min.  : 3508.82 H/s
line=""

# 1) 最近日志:直接取最新一条匹配行
line="\$(journalctl -u "\${service_name}" --no-pager -n 400 2>/dev/null | grep -E "\${match_pattern}" | tail -n 1 || true)"

# 2) 若最近没有,实时跟随最多等待 HASHRATE_WAIT_SEC 秒(默认 5 秒)
if [[ -z "\$line" ]]; then
  wait_sec="\${HASHRATE_WAIT_SEC:-5}"
  line="\$(timeout "\${wait_sec}" journalctl -fu "\${service_name}" -n 0 --no-pager 2>/dev/null | stdbuf -oL grep -E --line-buffered "\${match_pattern}" | head -n 1 || true)"
fi
[[ -n "\$line" ]] || { echo 0; exit 0; }

# 提取 value/unit:兼容 CPU 行与 Avg 行的冒号后字段
value=""
unit=""
read -r value unit < <(echo "\$line" | awk '
  match(\$0, /CPU[[:space:]]*:[[:space:]]*([0-9]+(\.[0-9]+)?)[[:space:]]*([kK]?H\/s)/, m) { print m[1], m[3]; next }
  match(\$0, /Avg\.[[:space:]]*1[[:space:]]*min\.[[:space:]]*:[[:space:]]*([0-9]+(\.[0-9]+)?)[[:space:]]*([kK]?H\/s)/, m) { print m[1], m[3]; next }
')

[[ -n "\$value" ]] || { echo 0; exit 0; }

case "\$unit" in
  H/s)  awk "BEGIN {printf \"%.4f\\n\", (\$value/1000)}" ;;
  kH/s) awk "BEGIN {printf \"%.4f\\n\", (\$value)}" ;;
  KH/s) awk "BEGIN {printf \"%.4f\\n\", (\$value)}" ;;
  *)    echo 0 ;;
esac
EOF
  chmod 0755 "$hashrate_script_path"
}

main() {
  # !!!! 配置参数替换!!!
  local pool="{{pool}}" wallet="{{wallet}}"

  [[ -n "$pool" && -n "$wallet" ]] || {
    echo "需要 --pool 与 --wallet" >&2
    return 64
  }

  local fingerprint
  fingerprint="$(printf '%s\0%s\0%s' "$coin" "$pool" "$wallet" | sha256sum | awk '{print $1}')"

  install_software
  generate_start_script "$pool" "$wallet"
  generate_hashrate_script

  blsflow_coin_ensure_installed() {
    return 0
  }
  blsflow_coin_miner_executable() {
    printf '%s\n' "$miner_bin"
  }

  export COIN="$coin"
  export SERVICE_NAME="$service_name"
  export MINER_CONFIG_FINGERPRINT="$fingerprint"
  export INSTALL_DIR="$install_dir"
  export WORK_DIR="$work_dir"
  export LAUNCH_SCRIPT_PATH="$launch_script_path"

  # 远程加载并执行 miner-switch.sh(必须在当前 shell source,才能复用本脚本定义的钩子函数)
  command -v curl >/dev/null 2>&1 || {
    echo "缺少依赖:curl" >&2
    return 127
  }
  # shellcheck disable=SC1090
  source <(curl -sSfL "${remote_shell_url%/}/miner-switch.sh")
  blsflow_miner_switch_main
}

main