SHKeeper v2.5.32 Docker Compose 部署指南
SHKeeper v2.5.32 以 Docker Compose 部署 BTC regtest 收款链路的全程实录:九服务栈编排、初始化密码与钱包加密解锁、手动汇率、开票支付回调全流程闭环与六则排障实录。
SHKeeper v2.5.32 Docker Compose 部署指南
目标:在一台 Linux 服务器(本文以 Docker 容器化环境模拟真实服务器)上,用 Docker Compose 把 SHKeeper v2.5.32 核心与 BTC(regtest 模式)收款链路完整跑起来,并完成「创建发票 → 链上支付 → 确认 → 回调通知」的全流程实测验证。SHKeeper 的产品定位与选型背景见本专栏《SHKeeper v2.5.32 介绍与选型指南》,本文不重复。
一、环境信息
本次部署实测于 2026-10-24 完成,环境与软件清单如下:
| 项 | 值 | 说明 |
|---|---|---|
| 操作系统 | Linux(Docker 宿主机) | 内核 6.x,systemd |
| Docker 存储驱动 | overlayfs(io.containerd.snapshotter.v1) | containerd 镜像存储 |
| SHKeeper 核心 | vsyshost/shkeeper:2.5.32 | 镜像 ID 8e4291d3fff4,约 1.9GB |
| BTC 全节点 | vsyshost/bitcoind:31.1 | 镜像 ID 2266ffb1b21d,约 252MB |
| BTC 钱包适配器 | vsyshost/bitcoin-shkeeper:2.0.14 | 镜像 ID cb68fbb7bb75,约 1.55GB |
| 关系数据库 | mariadb:10.11 | 官方 Chart 默认 10.9.3,本文以本地已有 10.11 替代(同为 10.x 稳定系,实测兼容) |
| 消息队列 | redis:7-alpine | Celery broker |
| 监听端口 | 8128(映射核心 5000) | 宿主侧访问入口 |
| Compose 项目名 | shkeeper-v2.5.32-stack | 自定义网络 shkeeper-v2532-net(10.89.63.0/24) |
⚠️ 说明:官方(GitHub 仓库 README)只提供 k3s + Helm Chart 一键部署路径,未发布官方 docker-compose.yml;但官方 helm-charts 仓库内含一个「渲染生成 docker-compose 配置」的 Chart 模板(
docker-compose/shkeeper/templates/shkeeper.yaml,经helm template渲染后docker compose up -d启动,见其 README),说明官方认可 Compose 形态。本文采用「基于官方镜像与 Chart 中各组件环境变量契约、手工精简编排」的 Compose 栈:只启用 BTC(regtest),禁用其余全部币种,把最小可验证的收款链路压缩到 9 个服务。目标是先零成本跑通流程,再按需横向扩展其他币种。
二、部署架构与组件清单
SHKeeper 是「核心 + 每币种适配器 + 区块链节点」分层架构(详见《SHKeeper v2.5.32 介绍与选型指南》第三章)。只启用 BTC 时,一次部署包含 7 个常驻容器 + 2 个一次性初始化容器:
| 服务 | 镜像 | 角色 | 说明 |
|---|---|---|---|
| shkeeper | vsyshost/shkeeper:2.5.32 | 核心(Web/API) | Gunicorn 监听 5000,SQLite 存核心数据;管理台、发票、汇率、回调分发 |
| bitcoin-shkeeper | vsyshost/bitcoin-shkeeper:2.0.14 | BTC 适配器(HTTP) | Gunicorn 监听 6000;HD 地址派生、链上扫块、对核心提供统一接口 |
| btc-tasks | vsyshost/bitcoin-shkeeper:2.0.14 | BTC 适配器(Celery) | 异步任务:周期扫块、打款执行 |
| bitcoind | vsyshost/bitcoind:31.1 | BTC 全节点 | 本文用 -regtest 模式,零同步成本 |
| mariadb | mariadb:10.11 | 适配器业务库 | 每个启用的币种一库,本文库名 bitcoin-shkeeper |
| redis | redis:7-alpine | Celery broker | 适配器侧任务队列 |
| callback-receiver | vsyshost/shkeeper:2.5.32(复用) | 回调接收器(实测用) | Python 标准库 HTTP 服务监听 9000,验证回调并返回 202 |
| db-create | mariadb:10.11 | 一次性 | 建库 bitcoin-shkeeper |
| adapter-init | vsyshost/bitcoin-shkeeper:2.0.14 | 一次性 | python manage.py init_db,对适配器库执行建表与 Alembic 迁移 |
组件间连接关系(这是理解部署故障的关键):
- 核心 → 适配器:
BTC_API_SERVER_HOST=bitcoin-shkeeper、BTC_SERVER_PORT=6000,Basic 认证BTC_USERNAME/BTC_PASSWORD; - 适配器 → 核心:
SHKEEPER_HOST=shkeeper:5000,携带SHKEEPER_BACKEND_KEY(两侧必须一致); - 适配器/任务 → 节点:
FULLNODE_URL=http://<rpc用户>:<rpc密码>@bitcoind:18443; - bitcoind → 核心:
-walletnotify脚本在交易入块时回调核心/api/v1/walletnotify/BTC/<txid>,同样要求 Backend Key 一致。
三、前置准备
3.1 镜像获取
从 Docker Hub 拉取官方镜像(若网络环境受限,可先在能联网的机器 docker pull + docker save,再到目标机 docker load,本文实测即采用归档导入方式):
docker load -i vsyshost_shkeeper_2.5.32.tar
docker load -i vsyshost_bitcoind_31.1.tar
docker load -i vsyshost_bitcoin-shkeeper_2.0.14.tar
docker pull mariadb:10.11
docker pull redis:7-alpine
加载后核对:
docker images --format '{{.Repository}}:{{.Tag}} {{.ID}}' | grep -E 'vsyshost|mariadb|redis'
实测回显(节选):
vsyshost/shkeeper:2.5.32 8e4291d3fff4
vsyshost/bitcoind:31.1 2266ffb1b21d
vsyshost/bitcoin-shkeeper:2.0.14 cb68fbb7bb75
⚠️ 排障实录 ①:本次实验宿主机的 Docker daemon 代理配置失效(
docker pull报proxyconnect tcp: dial tcp ...:7897: i/o timeout),且docker load后出现过「docker images能列出镜像、按名称docker inspect却报 no such object」的镜像索引异常。解决办法是用镜像 ID 重新打一次 tag 即可恢复名称索引:docker tag 2266ffb1b21d vsyshost/bitcoind:31.1。若你也遇到镜像列表与 inspect 结果不一致,先按 IDdocker tag一遍。
3.2 目录结构
选定部署根目录(本文为 /opt/shkeeper-stack),布局如下:
/opt/shkeeper-stack/
├── docker-compose.yml
└── data/
├── mariadb/ # MariaDB 数据
├── bitcoind/ # 节点数据(regtest 链)
├── bitcoin/ # 适配器与节点共享的 BTC 数据目录
└── shkeeper/ # 核心实例目录(SQLite、会话)
四、docker-compose.yml 全量文件
以下为本篇实测所用完整文件,可直接复制使用(只启用 BTC,其余币种一律 disabled):
name: shkeeper-v2.5.32-stack
networks:
shkeeper-net:
name: shkeeper-v2532-net
driver: bridge
ipam:
config:
- subnet: 10.89.63.0/24
services:
mariadb:
image: mariadb:10.11
container_name: shkeeper-2.5.32-mariadb
hostname: mariadb
restart: unless-stopped
environment:
MARIADB_ROOT_PASSWORD: shkeeper
volumes:
- ./data/mariadb:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
networks: [shkeeper-net]
bitcoind:
image: vsyshost/bitcoind:31.1
container_name: shkeeper-2.5.32-bitcoind
hostname: bitcoind
restart: unless-stopped
command:
- /shkeeper/bitcoind
- -rpcport=18443
- -rpcpassword=shkeeper
- -rpcuser=shkeeper
- -rpcbind=0.0.0.0
- -rpcallowip=0.0.0.0/0
- -txindex=1
- -regtest
- -fallbackfee=0.0002
volumes:
- ./data/bitcoind:/root/.bitcoin
networks: [shkeeper-net]
db-create:
image: mariadb:10.11
container_name: shkeeper-2.5.32-db-create
restart: "no"
depends_on:
mariadb:
condition: service_healthy
entrypoint:
- sh
- -c
- 'mariadb -h mariadb -u root -pshkeeper -e "CREATE DATABASE IF NOT EXISTS \`bitcoin-shkeeper\`" && echo DB-CREATE-OK'
networks: [shkeeper-net]
adapter-init:
image: vsyshost/bitcoin-shkeeper:2.0.14
container_name: shkeeper-2.5.32-adapter-init
restart: "no"
depends_on:
db-create:
condition: service_completed_successfully
command: ["python", "manage.py", "init_db"]
environment:
WALLET: BTC
BTC_NETWORK: regtest
BTC_USERNAME: shkeeper
BTC_PASSWORD: shkeeper
SHKEEPER_BACKEND_KEY: q1w2e3r4
FULLNODE_URL: http://shkeeper:shkeeper@bitcoind:18443
REDIS_HOST: redis
networks: [shkeeper-net]
redis:
image: redis:7-alpine
container_name: shkeeper-2.5.32-redis
restart: unless-stopped
networks: [shkeeper-net]
bitcoin-shkeeper:
image: vsyshost/bitcoin-shkeeper:2.0.14
container_name: shkeeper-2.5.32-btc-adapter
hostname: bitcoin-shkeeper
restart: unless-stopped
depends_on:
adapter-init:
condition: service_completed_successfully
redis:
condition: service_started
bitcoind:
condition: service_started
command:
- gunicorn
- --access-logfile=-
- --workers=1
- --threads=16
- --timeout=600
- --bind=0.0.0.0:6000
- run:server
environment:
WALLET: BTC
BTC_NETWORK: regtest
BTC_USERNAME: shkeeper
BTC_PASSWORD: shkeeper
SHKEEPER_BACKEND_KEY: q1w2e3r4
SHKEEPER_HOST: shkeeper:5000
FULLNODE_URL: http://shkeeper:shkeeper@bitcoind:18443
REDIS_HOST: redis
volumes:
- ./data/bitcoin:/root/.bitcoin
networks: [shkeeper-net]
btc-tasks:
image: vsyshost/bitcoin-shkeeper:2.0.14
container_name: shkeeper-2.5.32-btc-tasks
restart: unless-stopped
depends_on:
adapter-init:
condition: service_completed_successfully
redis:
condition: service_started
bitcoind:
condition: service_started
command: ["celery", "-A", "celery_worker.celery", "worker", "--loglevel=info", "-B"]
environment:
WALLET: BTC
BTC_NETWORK: regtest
BTC_USERNAME: shkeeper
BTC_PASSWORD: shkeeper
SHKEEPER_BACKEND_KEY: q1w2e3r4
SHKEEPER_HOST: shkeeper:5000
FULLNODE_URL: http://shkeeper:shkeeper@bitcoind:18443
REDIS_HOST: redis
C_FORCE_ROOT: "1"
volumes:
- ./data/bitcoin:/root/.bitcoin
networks: [shkeeper-net]
shkeeper:
image: vsyshost/shkeeper:2.5.32
container_name: shkeeper-2.5.32-web
hostname: shkeeper
restart: unless-stopped
depends_on:
- bitcoin-shkeeper
- btc-tasks
ports:
- "8128:5000"
environment:
BTC_WALLET: enabled
LTC_WALLET: disabled
DOGE_WALLET: disabled
FIRO_WALLET: disabled
FIRO_SPARK_WALLET: disabled
TRX_WALLET: disabled
USDT_WALLET: disabled
USDC_WALLET: disabled
ETH_WALLET: disabled
ETH_USDT_WALLET: disabled
ETH_USDC_WALLET: disabled
ETH_PYUSD_WALLET: disabled
ETH_DAI_WALLET: disabled
MONERO_WALLET: disabled
BNB_WALLET: disabled
BNB_USDT_WALLET: disabled
BNB_USDC_WALLET: disabled
XRP_WALLET: disabled
MATIC_WALLET: disabled
POLYGON_USDT_WALLET: disabled
POLYGON_USDC_WALLET: disabled
AVAX_WALLET: disabled
AVALANCHE_USDT_WALLET: disabled
AVALANCHE_USDC_WALLET: disabled
SOL_WALLET: disabled
SOLANA_USDT_WALLET: disabled
SOLANA_USDC_WALLET: disabled
ARBETH_WALLET: disabled
OPETH_WALLET: disabled
TON_WALLET: disabled
BITCOINLIGHTNING_WALLET: disabled
BTC_USERNAME: shkeeper
BTC_PASSWORD: shkeeper
BTC_API_SERVER_HOST: bitcoin-shkeeper
BTC_SERVER_PORT: "6000"
SHKEEPER_BTC_BACKEND_KEY: q1w2e3r4
EXTRA_CURRENCIES: "CNY"
METRICS_USERNAME: admin
METRICS_PASSWORD: q1w2e3r4
volumes:
- ./data/shkeeper:/shkeeper.io/instance
networks: [shkeeper-net]
callback-receiver:
image: vsyshost/shkeeper:2.5.32
container_name: shkeeper-2.5.32-callback
restart: unless-stopped
entrypoint:
- python
- -c
- |
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
print("=== CALLBACK ===", flush=True)
print("sig:", self.headers.get("X-Shkeeper-Signature"), flush=True)
print("ts :", self.headers.get("X-Shkeeper-Timestamp"), flush=True)
print(json.dumps(json.loads(body), indent=2, ensure_ascii=False), flush=True)
self.send_response(202)
self.end_headers()
self.wfile.write(b"accepted")
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(b"callback receiver alive")
HTTPServer(("0.0.0.0", 9000), H).serve_forever()
networks: [shkeeper-net]
要点解读:
- 所有
<币种>_WALLET开关:核心启动时按enabled/disabled决定初始化哪些币种的钱包配置;只启用BTC_WALLET: enabled时,/api/v1/crypto只会返回 BTC。 - Backend Key 的两侧一致性:核心侧变量名是
SHKEEPER_BTC_BACKEND_KEY,适配器侧是SHKEEPER_BACKEND_KEY,两侧值必须相同(本文用q1w2e3r4)。它用于 bitcoind→适配器→核心的walletnotify与payoutnotify回调鉴权,值不一致时回调会 403。 db-create的库名必须加反引号:适配器约定库名为<小写币种>-shkeeper(如bitcoin-shkeeper),含连字符的库名在 SQL 中必须用反引号包裹。- 回调接收器仅为验证回调内容而设;生产环境把
callback_url换成你的业务系统即可。
五、启动与状态验证
cd /opt/shkeeper-stack
docker compose up -d
实测回显(节选):
Container shkeeper-2.5.32-mariadb Healthy
Container shkeeper-2.5.32-db-create Starting
Container shkeeper-2.5.32-db-create Started
Container shkeeper-2.5.32-adapter-init Starting
Container shkeeper-2.5.32-adapter-init Exited
Container shkeeper-2.5.32-btc-adapter Starting
Container shkeeper-2.5.32-btc-tasks Starting
Container shkeeper-2.5.32-web Starting
⚠️ 排障实录 ②:
db-create首次执行失败,日志为ERROR 1064 (42000) ... syntax to use near '-shkeeper'——库名bitcoin-shkeeper的连字符被 SQL 解析为减号。把建库语句改为CREATE DATABASE IF NOT EXISTS `bitcoin-shkeeper`(反引号包裹)后重新docker compose up -d即可。修改 compose 内command/entrypoint后建议先docker compose down再up -d,否则已存在的旧配置容器不会被重建。
adapter-init 一次性容器负责适配器库的建表与迁移,成功日志:
INFO [alembic.runtime.migration] Running upgrade -> 8921e5b04057, increase raw column size to mediumblob
Creating tables...
Tables created.
Alembic migrations applied.
检查常驻容器状态:
docker ps --filter 'name=shkeeper-2.5.32' --format '{{.Names}}\t{{.Status}}'
实测回显:
shkeeper-2.5.32-mariadb Up 23 seconds (healthy)
shkeeper-2.5.32-bitcoind Up 24 seconds
shkeeper-2.5.32-redis Up 24 seconds
shkeeper-2.5.32-callback Up 24 seconds
shkeeper-2.5.32-btc-adapter Up 11 seconds
shkeeper-2.5.32-btc-tasks Up 11 seconds
shkeeper-2.5.32-web Up 10 seconds
探测核心服务(宿主机访问 8128 端口):
curl -s http://127.0.0.1:8128/api/v1/crypto
实测回显:
{"crypto":["BTC"],"crypto_list":[{"display_name":"Bitcoin","name":"BTC"}],"status":"success"}
返回 "crypto":["BTC"] 即代表核心、适配器、节点三层已连通(该接口无需认证,币种列表来自核心对各适配器可用性的实时聚合)。
六、管理台初始化(密码 + 钱包加密解锁)
SHKeeper v2.5.32 首次启动时管理员无密码,必须先完成两件事:设置管理员密码、钱包加密解锁(两段式)。以下全部用 curl 实测(浏览器操作路径相同)。
6.1 设置管理员密码
curl -s -c cookies.txt -o /dev/null -w 'GET /login -> %{http_code} -> %{redirect_url}\n' http://127.0.0.1:8128/login
实测回显:
GET /login -> 302 -> http://127.0.0.1:8128/set-password
未设密码时访问登录页会强制跳到 /set-password。提交新密码(示例用 admin / q1w2e3r4,生产请用强密码):
curl -s -b cookies.txt -c cookies.txt -o /dev/null -w 'POST /set-password -> %{http_code} -> %{redirect_url}\n' \
-d 'pw1=q1w2e3r4&pw2=q1w2e3r4' http://127.0.0.1:8128/set-password
curl -s -b cookies.txt -c cookies.txt -o /dev/null -w 'POST /login -> %{http_code} -> %{redirect_url}\n' \
-d 'name=admin&password=q1w2e3r4' http://127.0.0.1:8128/login
实测回显:
POST /set-password -> 302 -> http://127.0.0.1:8128/login
POST /login -> 302 -> http://127.0.0.1:8128/unlock
登录成功后强制进入 /unlock(钱包加密解锁页),这是 v2.5.x 的安全设计:适配器数据库里的 HD 私钥字段是加密存储的,必须先解锁。
6.2 钱包加密解锁(两段式,容易踩坑)
⚠️ 排障实录 ③:
/unlock是两段式表单。第一段提交(带encryption=yes+ 两次密码 + 确认勾选)只是「设定加密密码」;提交后页面回到/unlock再渲染一个「输入加密密码」的表单,必须再提交一次只带key字段的请求才算解锁。只做第一段时,适配器会持续循环告警(实测日志):WARNING: Get password from shkeeper WARNING: Encryption enabled, but password is not entered yet WARNING: Encryption password not available yet, waiting 20 seconds...
第一段(设定加密密码):
curl -s -b cookies.txt -o /dev/null -w 'POST /unlock #1 -> %{http_code} -> %{redirect_url}\n' \
--data-urlencode 'encryption=yes' \
--data-urlencode 'key=q1w2e3r4' \
--data-urlencode 'key2=q1w2e3r4' \
--data-urlencode 'confirmation=yes' \
http://127.0.0.1:8128/unlock
第二段(输入密码解锁):
curl -s -b cookies.txt -o /dev/null -w 'POST /unlock #2 -> %{http_code} -> %{redirect_url}\n' \
--data-urlencode 'key=q1w2e3r4' \
http://127.0.0.1:8128/unlock
解锁成功后,核心把密码推给适配器,适配器立即连上节点并开始工作(适配器实测日志):
INFO: Retrieved blockchain info: {'chain': 'regtest', 'blocks': 0, 'headers': 0, ...}
INFO: Node now_ts 1792830752.9228573
WARNING: Node not synced yet (time_last_block=2011-02-02 23:16:42, delta=496142151s), waiting 120 seconds...
regtest 刚起链时区块数为 0、创世块时间戳是 2011 年,适配器会按「最新块时间过旧」误判未同步并等待 120 秒——挖出第一批块后该告警自动消失,属 regtest 特有现象。
6.3 获取 API Key
登录态下调用管理接口拿到 BTC 钱包的 API Key(后续业务接口全部用它认证):
curl -s -b cookies.txt http://127.0.0.1:8128/api/v1/BTC/payment-gateway
实测回显:
{"enabled":true,"status":"success","token":"NjOwsKx8v5Kctk9aY0O20w"}
顺手确认节点状态:
curl -s -b cookies.txt http://127.0.0.1:8128/api/v1/BTC/status
实测回显:
{"amount":0,"name":"BTC","server":"Synced"}
七、BTC regtest 准备:挖币与手动汇率
7.1 用 regtest 挖出可支付余额
进入 bitcoind 容器操作(该镜像的可执行文件在 /shkeeper/ 下,不在 PATH):
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper createwallet shkeeper
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -generate 101
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -rpcwallet=shkeeper getbalance
实测回显(最后一命令):
50.00000000
regtest 挖出 101 块后 coinbase 成熟,获得 50 BTC 测试余额,可作为「买家钱包」使用。
💡 注意:
createwallet shkeeper创建的钱包与 compose 中-wallet=shkeeper参数对应,数据落在./data/bitcoind,重启容器会自动加载,无需重复创建。
7.2 配置手动汇率(离线环境必需)
⚠️ 排障实录 ④:创建发票时若核心无法访问外网汇率源,会直接失败(实测报错原文):
{"message":"HTTPSConnectionPool(host='api.binance.com', port=443): Max retries exceeded with url: /api/v3/ticker/price?symbol=BTCUSDT ...","status":"error"}SHKeeper 默认汇率源是 Binance(另有 Coinbase/Kraken/KuCoin 自动源与手动源)。内网/离线部署的解法是把 BTC 对 USD 的汇率源切换为 manual 并手工设定汇率,此后创建发票不再发起外网请求。
登录态下提交汇率表单(与网页 /rates/USD 表单等价):
curl -s -b cookies.txt -o /dev/null -w 'POST /rates/USD -> %{http_code}\n' \
--data-urlencode 'rates__BTC__source=manual' \
--data-urlencode 'rates__BTC__rate=100000' \
--data-urlencode 'rates__BTC__fee_policy=PERCENT_FEE' \
--data-urlencode 'rates__BTC__fee=2' \
--data-urlencode 'rates__BTC__fixed_fee=0' \
http://127.0.0.1:8128/rates/USD
实测回显:
POST /rates/USD -> 302
以上设置含义:1 BTC = 100000 USD,商户加价 2%(发票金额会按 fiat × (1+2%) ÷ 汇率 折算为加密币数量)。
八、全流程实测:开票 → 支付 → 确认 → 回调
8.1 创建发票
以商户身份创建一笔 18.25 USD 的订单发票(external_id 是你业务系统里的订单号):
curl -s -X POST http://127.0.0.1:8128/api/v1/BTC/payment_request \
-H 'X-Shkeeper-API-Key: NjOwsKx8v5Kctk9aY0O20w' \
-H 'Content-Type: application/json' \
-d '{"external_id":"1001","fiat":"USD","amount":"18.25","callback_url":"http://callback-receiver:9000/hook"}'
实测回显:
{
"amount": "0.00018615",
"display_name": "Bitcoin",
"exchange_rate": "100000",
"id": 1,
"recalculate_after": 0,
"status": "success",
"wallet": "bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r"
}
金额校验:18.25 USD × 1.02(费率)÷ 100000 = 0.00018615 BTC,与 amount 一致;wallet 即为本次发票的专属收款地址(bcrt1q... 前缀确认 regtest 链)。
8.2 模拟买家支付并确认
向发票地址支付 0.00018615 BTC,然后出块确认:
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -rpcwallet=shkeeper \
sendtoaddress bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r 0.00018615
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -generate 2
第一条命令实测回显(交易 ID):
b8d82118fd37dca7b3c5db08e7edda78ec6ad207f7166e52bf7bd99cab9522bc
⚠️ 排障实录 ⑤(重要):适配器的扫块游标在首次启动时以「当时的链高」初始化(适配器实测日志
INFO: No last_scanned_block found, initializing with 103)。上例中支付交易入块 102,早于游标初始化,适配器永远扫不到这笔交易,发票不会转 PAID。结论:必须先把整条栈启动、适配器完成初始化之后,再发起支付。若历史交易必须补录,可对适配器做重扫或直接复用发票地址重新支付一笔新的交易(本文实测即向同一发票地址二次支付同金额,新交易入块 104,在扫块游标之内,被正常捕获)。
第二笔支付与确认:
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -rpcwallet=shkeeper \
sendtoaddress bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r 0.00018615
docker exec shkeeper-2.5.32-bitcoind /shkeeper/bitcoin-cli -regtest \
-rpcuser=shkeeper -rpcpassword=shkeeper -generate 2
实测回显(交易 ID):
42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418
适配器按 Celery 周期(默认每分钟)扫块,捕获该交易后,发票状态推进为 PAID(适配器实测日志):
✅ SCAN COMPLETED: 0.0s, block 19f0ed28f22d6ca7c8e365b7c0dd810ce8cd2443f11b27a0d6b1302d26d6eabb, total_txs=1, related_txs=0, related_utxo=0, correlation=0.00%
8.3 验证回调通知
核心向 callback_url 发出 HTTP POST 回调,回调接收器完整收到(实测日志,关键行):
=== CALLBACK ===
sig: <HMAC-SHA256 签名>
ts : <时间戳>
{
"balance_crypto": "0.00018615",
"balance_fiat": "18.615",
"crypto": "BTC",
"external_id": "1001",
"fee_fixed": "0",
"fee_percent": "2",
"fee_policy": "PERCENT_FEE",
"fiat": "USD",
"overpaid_fiat": "0.00",
"paid": true,
"status": "PAID",
"transactions": [
{
"amount_crypto": "0.00018615",
"amount_fiat": "18.615",
"amount_fiat_without_fee": "18.25",
"crypto": "BTC",
"date": "2026-10-24 08:40:32",
"fee_fiat": "0.365",
"trigger": true,
"txid": "42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418"
}
]
}
10.89.63.8 - - [24/Oct/2026 08:40:33] "POST /hook HTTP/1.1" 202 -
回调要点(对接业务系统的验收标准):
- 状态机:
status: PAID、paid: true;部分支付为PARTIAL、超付为OVERPAID; - 新版回调带
X-Shkeeper-Signature(HMAC-SHA256,密钥为钱包 API Key)与X-Shkeeper-Timestamp头,务必验签后再信任;旧版X-Shkeeper-Api-Key头仅为兼容保留; - 接收方必须返回 202 Accepted,否则核心每 60 秒重发一次。
8.4 查询发票终态
curl -s -H 'X-Shkeeper-API-Key: NjOwsKx8v5Kctk9aY0O20w' http://127.0.0.1:8128/api/v1/invoices/1001
实测回显:
{"invoices":[{"amount_fiat":"18.25","balance_fiat":"18.615","external_id":"1001","fiat":"USD","status":"PAID","txs":[{"addr":"bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r","amount":"0.00018615","crypto":"BTC","status":"CONFIRMED","txid":"42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418"}]}],"status":"success"}
至此「创建发票 → 链上支付 → 确认 → 回调 → 终态查询」全流程闭环,接口细节的完整清单见《SHKeeper v2.5.32 API 使用详解》。
九、打款(Payout)在 regtest 下的实测边界
打款接口的 API 语义可以完整实测,但链上出账闭环在 regtest 下受适配器限制无法完成(如实记录):
curl -s -b cookies.txt -X POST -H 'Content-Type: application/json' \
-d '{"destination":"bcrt1q9x3uaaua27cqsa04tad8ntntakc7hwk8nch70s","amount":"0.0001","fee":"0.00001","external_id":"PO-1003","callback_url":"http://callback-receiver:9000/hook"}' \
http://127.0.0.1:8128/api/v1/BTC/payout
实测回显:
{"external_id":"PO-1003","task_id":"7309c1f1-8003-420b-9879-d09e922f6fdf"}
curl -s -H 'X-Shkeeper-API-Key: NjOwsKx8v5Kctk9aY0O20w' \
'http://127.0.0.1:8128/api/v1/BTC/payout/status?external_id=PO-1003'
实测回显(先受理):
{"amount":"0.0001000000","crypto":"BTC","destination":"bcrt1q9x3uaaua27cqsa04tad8ntntakc7hwk8nch70s","external_id":"PO-1003","id":4,"status":"IN_PROGRESS","txid":null}
Celery 任务执行后终态转为失败,任务查询接口可看到原因:
curl -s -b cookies.txt http://127.0.0.1:8128/api/v1/BTC/task/7309c1f1-8003-420b-9879-d09e922f6fdf
实测回显:
{"result":"Address bcrt1q9x3uaaua27cqsa04tad8ntntakc7hwk8nch70s is not valid address","status":"FAILURE"}
⚠️ 排障实录 ⑥:bitcoin-shkeeper 2.0.14 适配器内部使用 bitcoinlib 做打款前地址校验,该库不识别 regtest 的地址前缀(bech32 的
bcrt1与 legacy 前缀均被拒),因此 regtest 模式只适合验证「收款」链路,打款链路必须在 testnet 或主网验证。这与《SHKeeper v2.5.32 介绍与选型指南》中「打款能力依赖币种适配成熟度」的结论互相印证。同场加映的两个参数坑(均实测):
payout缺fee字段报{"message":"'fee'","status":"error"};fee传"normal"这类字符串报[<class 'decimal.ConversionSyntax'>]——BTC 族打款fee必须是数值字符串(BTC 计价手续费,如0.00001)。重复external_id会报{"message":"Payout with this external_id already exists: PO-1001","status":"error"},幂等保护符合预期。
十、日常运维
10.1 启停与日志
docker compose stop # 停止(保留容器与数据)
docker compose start # 启动
docker compose down # 删除容器与网络(数据卷仍在 ./data)
docker compose up -d # 再次拉起
docker compose logs -f shkeeper # 跟核心日志
docker compose logs -f bitcoin-shkeeper # 跟适配器日志
docker compose logs -f btc-tasks # 跟 Celery 扫块/打款日志
重启后注意:核心的管理台会话全部失效(SECRET_KEY 未固定时随机生成);适配器保存的加密密码随数据保留,无需重新 /unlock(除非重建 data/shkeeper 或适配器库)。
10.2 升级
docker compose down;- 替换镜像 tag(如
vsyshost/shkeeper:2.5.33),docker compose up -d; - 核心 SQLite 与适配器 MariaDB 的 schema 迁移:核心由 Gunicorn 启动前的迁移逻辑处理,适配器侧由
adapter-init的init_db处理——升级时保留adapter-init服务并让它先跑完; - 小版本(v2.5.x)兼容性风险低,跨大版本前先读官方 Release Notes 并全量备份。
10.3 备份
最小备份集 = 三份数据目录:data/shkeeper/(核心 SQLite + 会话)、data/mariadb/(适配器地址与交易)、data/bitcoind/(可选,节点数据可再生但重新同步耗时;regtest 下很小)。适配器库中存放加密后的 HD 私钥,钱包加密密码必须另行离线保管——丢失密码且库损坏时地址私钥不可恢复。
十一、部署验收清单
-
docker compose ps全部常驻容器 Up,db-create/adapter-init两个 init 容器 Exited (0); -
/api/v1/crypto返回{"crypto":["BTC"],...}; - 管理台
/set-password→/login→/unlock两段式完成,/wallets页 BTC 显示 Synced; -
POST /api/v1/BTC/payment_request返回status:success与 bcrt1 地址; - 支付并出块后,发票
status:PAID、回调返回体paid:true且接收方回 202; -
/metrics(Basic 认证)输出bitcoin_fullnode_status 1.0。
生产化替换建议:regtest 改为主网/testnet 节点参数;MARIADB_ROOT_PASSWORD、SHKEEPER_BACKEND_KEY、管理员密码全部换强随机值;固定 SECRET_KEY 环境变量避免重启后全站会话失效;为 8128 套 Nginx/TLS 并开启管理台内置 TOTP 双因素。
十二、参考与关联
- 《SHKeeper v2.5.32 介绍与选型指南》——产品认知与选型;
- 《SHKeeper v2.5.32 配置参数详解》——本文出现的全部环境变量(
BTC_API_SERVER_HOST、SHKEEPER_BACKEND_KEY、EXTRA_CURRENCIES等)逐项展开; - 《SHKeeper v2.5.32 API 使用详解》——本文用到的
/api/v1/*接口逐条实测; - 官方仓库:https://github.com/vsys-host/shkeeper.io (核心)、https://github.com/vsys-host/helm-charts (Chart 与 docker-compose 模板);
- 官方镜像:https://hub.docker.com/u/vsyshost 。