创建自定义项目
如果你希望由 Codex 协助收集项目资料并自动创建项目、矿池和运行脚本,请先阅读使用 MCP 创建自定义项目。本文主要介绍手工创建时的脚本修改与验收要求。
创建自定义项目时,建议复制一份已经可用的项目脚本,只修改本项目相关的配置。
重点
只修改“项目配置、项目启动命令、算力解析”三部分。不要修改脚本后半部分的 BLSFlow 通用切换流程。
创建项目
进入 BLSFlow 控制台的“项目”页面,点击“新建自定义项目”,填写项目名称、项目符号、算法和支持系统。
添加矿池后,至少定义一个钱包参数:
| 字段 | 建议值 |
|---|---|
| 参数名称 | 钱包地址 |
| 显示名称 | wallet |
| 类型 | string |
| 是否必填 | 必填 |
脚本中可以使用:
wallet:钱包地址占位符,在执行时平台自动替换配置的值。
脚本必须修改的地方
1. 项目符号
把 coin 改为新项目的符号。应与控制台中填写的项目符号一致,建议仅使用小写字母、数字和短横线。
readonly coin="xmr"service_name、work_dir、launch_script_path 和 hashrate_script_path 会根据 coin 自动生成,不要分别写死成其他项目。
2. 项目软件
修改项目可执行文件名:
readonly miner_bin="${install_dir}/SRBMiner-MULTI"同时修改安装脚本的软件名:
curl -sSfL "$remote_shell_url/install_software.sh" | bash -s -- srbminer-multi这两处必须匹配。切换前 BLSFlow 会检查 miner_bin 是否存在且可执行。
3. 项目启动命令
在 generate_start_script 中修改 exec 命令,使用新项目的算法和项目参数:
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 中修改:
readonly match_pattern='CPU[[:space:]]*:|Avg\. 1 min\.'以及后面从日志中提取 value 和 unit 的 awk 规则。
hashrate.sh 的最终输出必须满足:
- stdout 只输出一个数值。
- 单位统一为 kH/s。
- 服务未运行或暂无算力时输出
0。
通常不需要修改的地方
如果新项目的钱包、矿池配置方式与示例相同,保留以下代码:
main() {
local pool="{{pool_url}}"
local wallet="{{wallet}}"
# 其余流程保持不变
}只有当项目确实需要额外参数时,才在控制台新增参数定义,并在这里读取对应的 。
不能修改的地方
以下代码是 BLSFlow 通用切换契约,不要删除、改名或改变执行顺序。
不要改动钩子函数名
blsflow_coin_ensure_installed() {
return 0
}
blsflow_coin_miner_executable() {
printf '%s\n' "$miner_bin"
}blsflow_coin_miner_executable 必须输出项目可执行文件的绝对路径。
不要删除环境变量
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 改成子进程执行
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未被改动。
先在一台测试设备上切换。任务显示“切币完成”后,检查:
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确认目标服务正常运行、矿池和钱包正确、算力能正常输出后,再用于其他设备。
完整示例
#!/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