[
  {
    "path": "Docs/GRID.md",
    "content": "## 汇率网格交易产品说明文档\n\n### 初始化汇率网格必填参数，举例ETH/BTC汇率交易对\n#### 需用户设置的参数：（也就是说开启网格功能只需设置格子数量参数即可）\n1. 最小汇率值 - 0.025    // 即网格下限， 对应我们的开仓条件\n2. 最大汇率值 - 0.0275   // 即网格上限， 对应我们的清仓条件\n3. 最大仓位价值 - 1000U   // 用户自己设置\n4. 格子数量 - 5          // 用户自己设置\n\n#### 无需用户设置，通过计算得出的数据：\n5. 每格汇率差 - 0.0005    // (最大汇率值 - 最小汇率值) / 格子数量 = 0.0005\n6. 每格利润率 - 1.9%      // 2 * 每格汇率差 / (最小汇率值 + 最大汇率值) = 0.01904761904\n7. 每格资金占用 - 200U    // 最大仓位价值 / 格子数量 = 200, 此值一定要大于单笔最小下单额（Binance BTC最小单就110u了），最好是2倍以上，否则可能会来回开单，迅速将本金磨损光\n\n(建议设置最小单小一点，比如10-20U这样会滑点会更低，因为网格交易不需要抢单，有足够时间吃满一个格子)\n\n#### 网格布局\n0.0275 ------------  网格上限 \\\n            第1格\\\n0.0270 ------------\\\n            第2格\\\n0.0265 ------------\\\n            第3格\\\n0.0260 ------------\\\n            第4格\\\n0.0255 ------------\\\n            第5格\\\n0.0250 ------------  网格下限\n\n### 初始建仓行为, 原地仅开其上方一个格子仓位\n假设网格启动时汇率为0.026111，刚好落在第3格，我们会买满第2格，占用总资金20%\n\n0.0275 ------------  网格上限\\\n            第1格 - 空仓\\\n0.0270 ------------\\\n            第2格 - 满仓，均价0.02611\\\n0.0265 ------------\\\n            第3格 - 空仓       \\\n0.0260 ------------\\\n            第4格 - 空仓\\\n0.0255 ------------\\\n            第5格 - 空仓\\\n0.0250 ------------  网格下限\n\n### 买入卖出时机\n买入时机一定是位于某格子下边缘以下（已超出此格子）才买入此格子，比如第一个格子，一定是0.027以下才买入\n卖出时机一定是位于某格子上边缘以上（已超出此格子）买卖出此格子，比如第一个格子，一定是0.0275以上才卖出\n实时汇率会上下游动，假设我们当前格子索引是N，我们只做两件事：\n1. 判断当前格子N下方一个格子，即索引N+1的格子，若满仓则清仓；空仓则无事发生\n2. 判断当前格子N上方一个格子，即索引N-1的格子，若空仓则买入；满仓则无事发生\n注意：单边上涨行情会出现几乎同时买卖情况发生，这不是BUG，其实是换仓\n\n### 格子状态：\n已买入：买入均价\n空仓：\n注意：某个格子买/卖出去一部分，那么状态不要更新，保持之前的状态，必须买/卖全部格子价值才算完成，才可以更新状态。\n\n### 利润统计：\n每当格子由满仓状态变为空仓状态时统计此格利润，并记录一条利润数据。 \n"
  },
  {
    "path": "Docs/STEP.md",
    "content": "# 梯度开清示例\n\n### 1. 对于 SF(现期)， FF(期期)模式\n示例 (可以直接复制或编辑后复制， 在Astro开单页面导入即可)：\n\n```\nASTRO-QUICK-COPY:\n{\n  \"type\": \"SF\",\n  \"name\": \"ETH\",\n  \"openPosition\": \"10\",\n  \"closePosition\": \"-10\",\n  \"maxTradeUSDT\": \"3000\",\n  \"buyEx\": \"binance\",\n  \"sellEx\": \"binance\",\n  \"leverage\": \"4\",\n  \"minNotional\": \"20\",\n  \"maxNotional\": \"60\",\n  \"startTime\": \"0\",\n  \"disableClose\": false,\n  \"disableOpen\": false,\n  \"stopLoss\": \"\",\n  \"stepOpen\": [\n    {\n      \"position\": 0.01,\n      \"limit\": 1000\n    },\n    {\n      \"position\": 0.02,\n      \"limit\": 2000\n    },\n    {\n      \"position\": 0.03,\n      \"limit\": 3000\n    }\n  ],\n  \"stepClose\": [\n    {\n      \"position\": 0.02,\n      \"limit\": 2000\n    },\n    {\n      \"position\": 0.01,\n      \"limit\": 1000\n    },\n    {\n      \"position\": 0,\n      \"limit\": 0\n    }\n  ]\n}\n```\n\n### 2. 对于 SR(现汇)， FR(期汇)模式\n示例 (可以直接复制或编辑后复制， 在Astro开单页面导入即可)：\n\n```\nASTRO-QUICK-COPY:\n{\n  \"type\": \"SR\",\n  \"name\": \"BTC-USDC\",\n  \"openPosition\": \"0\",\n  \"closePosition\": \"Infinity\",\n  \"maxTradeUSDT\": \"5000\",\n  \"buyEx\": \"binance\",\n  \"sellEx\": \"binance\",\n  \"leverage\": \"4\",\n  \"minNotional\": \"20\",\n  \"maxNotional\": \"60\",\n  \"startTime\": \"0\",\n  \"rateMultiply\": \"1\",\n  \"disableClose\": false,\n  \"disableOpen\": false,\n  \"stopLoss\": \"\",\n  \"stepOpen\": [\n    {\n      \"position\": 72000,\n      \"limit\": 1000\n    },\n    {\n      \"position\": 71500,\n      \"limit\": 2000\n    },\n    {\n      \"position\": 71000,\n      \"limit\": 3000\n    },\n    {\n      \"position\": 70500,\n      \"limit\": 4000\n    },\n    {\n      \"position\": 70000,\n      \"limit\": 5000\n    }\n  ],\n  \"stepClose\": [\n    {\n      \"position\": 70500,\n      \"limit\": 4000\n    },\n    {\n      \"position\": 71000,\n      \"limit\": 3000\n    },\n    {\n      \"position\": 71500,\n      \"limit\": 2000\n    },\n    {\n      \"position\": 72000,\n      \"limit\": 1000\n    },\n    {\n      \"position\": 72500,\n      \"limit\": 0\n    }\n  ]\n}\n```\n\n\n"
  },
  {
    "path": "Docs/常见问题.md",
    "content": "### ----- 如果查阅完此文档， 问题依然没有解决， 请TG私信群主寻求帮助 -----\n\n#### 1. 为什么安装完成后打不开页面？\n请使用最新版本Chrome浏览器  \\\n请复制HTTPS链接地址，不要手动输入, 注意是https不是http  \\\n若提示「您的连接不是私密连接」，请点击「高级」-> 「继续前往x.x.x.x（不安全）」 \\\n安全组必须放开TCP 12345端口，如果不知道如何放开，请联系云厂商客服。 \\\n如何验证12345端口是否已经开放？ \\\n使用命令 ```telnet 1.1.1.1 12345```  (请将1.1.1.1替换为你的云服务器IP地址) \\\n如下图所示，出现 Connected to，则说明端口已成功开放了 \\\n![image](https://github.com/user-attachments/assets/0c247e69-1bd2-46ad-a54c-0da627ea08dc)\n\n\n#### 2. Gate无法买入现货是为什么？\n很可能是因为启用了“余币宝”功能，此功能可将理财资产当做保证金开合约，但不能购买现货。 \\\n请切换到现货购买页面查看可用USDT数量是否正常\n\n#### \n"
  },
  {
    "path": "INSTALL.md",
    "content": "### [Astro产品介绍](./README.md) \n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECURITY.md) \n\n套利策略，产品使用群 \\\nhttps://t.me/astro_discuss\n\n行情工具 \\\nhttps://pulse.astro-btc.xyz/ \\\nhttps://astro-btc.github.io/Astro-Perps/?coin=ETH\n\n实时资讯, 上新，费率调整等 \\\nhttps://t.me/astro_realtime_news\n\n--------------------------------\n\n# Astro - 安装教程\n\n### 1. 云服务器要求\n切记不可以使用中国境内服务器，推荐阿里云，亚马逊云香港，日本地区 \\\n**境外网络完全可以本地部署，交易所KEY需要绑定IP，请注意IP变化**  \\\n**请不要用美国地区服务器，Bybit不让美国IP使用API** \n\n操作系统: ```Ubuntu系统``` \\\n系统架构：```x86-64``` \\\n内存：```最少2GB``` \n\n### 2. 执行一键安装脚本 (需确认公网IP地址)， 安装完成后请使用最新版Chrome浏览器打开（其他浏览器会有兼容性问题）\n```\ncurl -L https://raw.githubusercontent.com/astro-btc/Astro/refs/heads/main/install.sh | sudo bash -\n```\n\n### 3. astro-server/.env 文件字段说明\n\n| **配置项**               | **说明**                                                        |\n|--------------------------|-----------------------------------------------------------------|\n| `PORT`                   | 端口号，需要防火墙放行此端口 （默认8443暂不支持修改）                         |\n| `ALLOWED_DOMAIN`         | 云服务公网IP地址，也可以填域名（填写域名需替换证书）                   |\n| `ADMIN_PREFIX`           | 管理后台访问的 URL 后缀 （请自行更改， 最少4个字符）                             |\n| `ADMIN_SECURITY_CODE`    | 登录密码  （登录后，点击头像可修改密码）                                      |\n| `ADMIN_2FA_SECRET`       | 二次认证密钥，请导入 Google Authentication 使用 （登录后，点击头像可修改密码）    |\n\n\n此配置文件修改过后，请重启系统生效 \n\n### 4. 如何配置交易所API？\n‼️ 请务必每一个api都添加IP地址白名单 ‼️  \\\n‼️ 请务必 **不要** 开通[提现]权限 ‼️ \n\n#### a. Binance\n合约账户类型必须是 **统一账户**, 权限相关参考下图：\\\n（请先调整为统一账户，然后再创建API）\n![](images/BN-api.png)\n\n#### b. Bybit\n保证金模式设置为： **全仓保证金** \\\n权限相关参考下图：\\\n![](images/Bybit-API.png)\n\n#### c. Bitget\n支持统一账户 & 经典账户 \\\n请使用联合保证金模式， 并开启双向持仓，权限相关参考下图：\\\n![](images/BG-API.png)\n\n#### d. OKX\n请使用 「合约模式】 或 「跨币种保证金模式」，并开启双向持仓\n权限相关参考下图：\\\n![](images/OKX-API.png)\n\n#### e. Gate\n ==> 请开启双向持仓(交易设置->交易配置->持仓模式选择双向持仓)  \\\n ==> 请使用 **统一账户 + 跨币种保证金模式**，权限相关参考下图：\\\n![](images/Gate-API.png)\n\n#### f. Kucoin\n==> 请开启双向持仓 \\\n权限相关参考下图：\\\n![](images/kucoin-API.png)\n\n#### g. Aster\n联合保证金模式 + 双向持仓\n\n#### h. Backpack🎒\nhttps://backpack.exchange/portfolio/settings/api-keys\n\n#### i.Hyperliquid\n需要三个数据，主钱包地址，代理钱包地址，代理钱包私钥，请参考下面两张图：\\\n![image](https://github.com/user-attachments/assets/a8676428-a43d-460c-a183-f544ec0d2196) \\\n![image](https://github.com/user-attachments/assets/e52b0c84-29ca-4e66-8af3-5ae76840a4b4)\n\n#### j. Htx\n==> 请设置资产模式为联合保证金模式（资产模式->联合保证金模式）\\\n==> 请设置为双向持仓 （持仓模式->双向持仓）\n权限相关参考下图：\\\n![](images/htx1.png) \\\n![](images/htx2.png)\n\n#### k. Lighter\n1. 切勿勾选「仅减仓」选项， 否则无法开单 \\\nAPI KEY 相关参考下图：\\\n![](images/lighter1.png) \\\n![](images/lighter2.png)\n\n"
  },
  {
    "path": "README.md",
    "content": "\n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECURITY.md) \n\n请仔细阅读产品文档：  https://www.notion.so/Astro-2900e967938c80419830e5baf64f00f5\n"
  },
  {
    "path": "SDK-API.md",
    "content": "# Astro SDK 接口文档\n\n本文档主要说明 `ASTRO SDK API` 相关接口的调用方式。 代码参考 [demo](./sdk-demo.js)\n\n## 0. 设置API KEY\n// API Key - 需要先在账户->开发者代码通过「set api-key ***」设置API KEY, 随机数长度12-32，仅数字和大小写字母\n// ⚠️ 如果api key被黑客盗取，黑客就可以开单了，切记谨慎保管，定期更换，长度尽量长\n\n[生成随机数网址](https://astro-btc.github.io/random/)\n\n\n## 1. 接口概览\n\n- 请求方式：`POST`\n- Pair 管理接口：`/api/config/sdk-update-pair`\n- Message 发送接口：`/api/config/sdk-send-message`\n- Pair 接口示例完整地址：`https://127.0.0.1:12345/api/config/sdk-update-pair`\n- Message 接口示例完整地址：`https://127.0.0.1:12345/api/config/sdk-send-message`\n- Pair 接口支持动作：`list`、`add`、`update`、`delete`\n- Message 接口支持类型：`warning`、`notice`\n\n说明：\n\n- `sdk-update-pair` 是统一入口接口，通过请求体中的 `action` 区分具体操作。\n- `list` 不需要传 `pair`。\n- `add`、`update`、`delete` 需要传 `pair` 对象。\n- `sdk-send-message` 用于发送文本消息，通过请求体中的 `type` 区分 `warning` 和 `notice`。\n\n## 2. 访问限制\n\n- 限频：`20次/10s`\n- 限频维度：按客户端 IP 统计\n- 超限响应：HTTP `429`\n\n超限返回示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"The rate limit has been reached.\"\n}\n```\n\n## 3. 鉴权与签名\n\n### 3.1 请求头\n\n每次请求都需要带上以下请求头：\n\n| Header | 必填 | 说明 |\n| --- | --- | --- |\n| `Content-Type` | 是 | 固定为 `application/json` |\n| `x-timestamp` | 是 | 当前毫秒时间戳 |\n| `x-nonce` | 是 | 随机字符串，建议每次请求唯一 |\n| `x-sign` | 是 | 请求签名 |\n\n### 3.2 API Key\n\n服务端需要先配置 API Key。若未配置，会返回：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"please run「set api-key xxx」in dev code\"\n}\n```\n\n### 3.3 时间戳要求\n\n- `x-timestamp` 必须是毫秒时间戳\n- 与服务端时间误差不能超过 `30 秒`\n- 超出范围会返回 HTTP `401`\n\n失败示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"bad x-timestamp, please adjust your time!\"\n}\n```\n\n### 3.4 Nonce 要求\n\n- 格式：`12-64` 位\n- 允许字符：大小写字母、数字、`_`、`-`\n- 同一个 `nonce` 不能重复使用，否则会被判定为重放请求\n\n重放请求返回示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"replay detected\"\n}\n```\n\n### 3.5 签名算法\n\n签名算法：\n\n- `HMAC-SHA256`\n- HMAC 密钥：`API Key`\n- 输出格式：小写十六进制字符串\n\n其中 `payload` 的构造规则如下：\n\n- `list`：`{\"action\":\"list\"}`\n- `add`：`{\"action\":\"add\",\"pair\":{...}}`\n- `update`：`{\"action\":\"update\",\"pair\":{...}}`\n- `delete`：`{\"action\":\"delete\",\"pair\":{...}}`\n- `warning`：`{\"type\":\"warning\",\"text\":\"...\"}` \n- `notice`：`{\"type\":\"notice\",\"text\":\"...\"}`\n\n签名消息使用如下 canonical message：\n\n```text\n${timestamp}\\n${nonce}\\nPOST\\n${apiPath}\\n${rawBody}\n```\n\n字段说明：\n\n- `timestamp`：请求头中的 `x-timestamp`\n- `nonce`：请求头中的 `x-nonce`\n- `POST`：当前接口固定为 `POST`\n- `apiPath`：当前请求的接口路径，例如 `/api/config/sdk-update-pair` 或 `/api/config/sdk-send-message`\n- `rawBody`：请求体原始 JSON 字符串，签名前后必须完全一致\n\n注意：\n\n- 服务端基于原始请求体 `rawBody` 验签，不会将解析后的对象重新序列化后再计算签名。\n- 客户端必须先构造最终请求 JSON 字符串，再对这个字符串签名，然后把同一份字符串作为 HTTP Body 发出。\n\nNode.js 示例：\n\n```js\nconst crypto = require('crypto');\n\nfunction signRequest(apiKey, nonce, timestamp, apiPath, rawBody) {\n  const canonicalMessage = [\n    String(timestamp),\n    String(nonce),\n    'POST',\n    apiPath,\n    rawBody\n  ].join('\\n');\n\n  return crypto\n    .createHmac('sha256', String(apiKey))\n    .update(canonicalMessage)\n    .digest('hex');\n}\n```\n\n## 4. 请求/响应格式\n\n### 4.1 Pair 接口请求体\n\n统一结构：\n\n```json\n{\n  \"action\": \"list | add | update | delete\",\n  \"pair\": {}\n}\n```\n\n说明：\n\n- 当 `action = list` 时，不需要 `pair`\n- 当 `action = add/update/delete` 时，必须传 `pair`\n\n### 4.2 Message 接口请求体\n\n统一结构：\n\n```json\n{\n  \"type\": \"warning | notice\",\n  \"text\": \"要发送的字符串\"\n}\n```\n\n说明：\n\n- `type = warning` 时，服务端会写入：`[uiw]-` + `text`\n- `type = notice` 时，服务端会写入：`[uin]-` + `text`\n- `text` 必须是非空字符串，服务端会做 `trim()`\n\n### 4.3 成功响应\n\n成功时业务码为：\n\n```json\n{\n  \"code\": 0\n}\n```\n\n不同动作的 `data` 不同，下面分别说明。\n\n### 4.4 失败响应\n\n常见失败格式：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"错误信息\"\n}\n```\n\n## 5. Pair 对象字段\n\n`add` 和 `update` 使用的 `pair` 对象，至少应包含下面这些常用字段。\n\n| 字段 | 类型 | 是否常用必填 | 说明 |\n| --- | --- | --- | --- |\n| `id` | `string` | `update/delete` 必填 | 10 位字母数字 ID |\n| `name` | `string` | 是 | 交易对名称，如 `ETH` |\n| `type` | `string` | 是 | 支持 `SF`、`FF`、`SR`、`FR`、`FS` |\n| `status` | `boolean` | 是 | 是否启用 |\n| `openPosition` | `string` | 是 | 开仓阈值 |\n| `closePosition` | `string` | 是 | 平仓阈值 |\n| `disableOpen` | `boolean` | 是 | 是否禁开仓 |\n| `disableClose` | `boolean` | 是 | 是否禁平仓 |\n| `maxTradeUSDT` | `string` | 是 | 最大交易额度，要求 `>= 10` |\n| `leverage` | `string` | 是 | 杠杆 |\n| `buyEx` | `string` | 是 | 买入交易所 |\n| `sellEx` | `string` | 是 | 卖出交易所 |\n| `startTime` | `string` | 否 | 启动时间 |\n| `minNotional` | `string` | 否 | 最小名义价值，若传则要求 `>= 8` |\n| `maxNotional` | `string` | 否 | 最大名义价值，若传则要求 `>= minNotional` |\n| `stopLoss` | `string` | 否 | 止损参数 |\n| `rateMultiply` | `string/number` | 否 | 额外倍率参数 |\n| `stepOpen` | `array` | 否 | 梯度开仓配置 |\n| `stepClose` | `array` | 否 | 梯度平仓配置 |\n| `priceAlert` | `string/number` | 否 | 价格告警参数 |\n| `adjustParams` | `object` | 否 | 动态调整参数 |\n| `usdcNoTrade` | `boolean` | 否 | 部分类型可用 |\n| `spotMarginType` | `string` | 否 | `FS` 类型可传：`spot`、`cross`、`isolated` |\n\n说明：\n\n- `add` 时服务端会自动生成 `id`，客户端可不传。\n- `update` 时必须传合法 `id`，否则返回 `bad pair id for update`。\n- `delete` 时只需要 `pair.id` 即可，但传完整对象也不会影响签名。\n- 示例脚本里的 `pair` 是最基础、最常用的一组字段，不代表全部可选字段。\n\n## 6. 接口明细\n\n### 6.1 查询列表\n\n用于获取当前所有 `pair` 配置。\n\n请求体：\n\n```json\n{\n  \"action\": \"list\"\n}\n```\n\n成功响应示例：\n\n```json\n{\n  \"code\": 0,\n  \"data\": [\n    {\n      \"id\": \"Ab12Cd34Ef\",\n      \"name\": \"ETH\",\n      \"type\": \"SF\",\n      \"status\": false,\n      \"openPosition\": \"0.0158\",\n      \"closePosition\": \"0.0022\",\n      \"disableOpen\": false,\n      \"disableClose\": false,\n      \"maxTradeUSDT\": \"1000\",\n      \"leverage\": \"1\",\n      \"buyEx\": \"binance\",\n      \"sellEx\": \"binance\",\n      \"startTime\": \"0\",\n      \"minNotional\": \"12\",\n      \"maxNotional\": \"30\"\n    }\n  ]\n}\n```\n\n### 6.2 新增 Pair\n\n用于新增一个新的 `pair` 配置。注意新增pair会导致astro-core进程重启，因此此动作完成后需要等待3秒再执行其他动作。\n\n请求体示例：\n\n```json\n{\n  \"action\": \"add\",\n  \"pair\": {\n    \"name\": \"ETH\",\n    \"status\": false,\n    \"type\": \"SF\",\n    \"openPosition\": \"0.0158\",\n    \"disableOpen\": false,\n    \"closePosition\": \"0.0022\",\n    \"disableClose\": false,\n    \"maxTradeUSDT\": \"1000\",\n    \"leverage\": \"1\",\n    \"buyEx\": \"binance\",\n    \"sellEx\": \"binance\",\n    \"startTime\": \"0\",\n    \"minNotional\": \"12\",\n    \"maxNotional\": \"30\"\n  }\n}\n```\n\n成功响应示例：\n\n```json\n{\n  \"code\": 0,\n  \"data\": null\n}\n```\n\n说明：\n\n- 服务端会自动生成 `pair.id`\n- 如果名称重复、字段不合法或参数校验失败，会返回 HTTP `400`\n\n### 6.3 更新 Pair\n\n用于更新已有 `pair`。\n\n请求体示例：\n\n```json\n{\n  \"action\": \"update\",\n  \"pair\": {\n    \"id\": \"Ab12Cd34Ef\",\n    \"name\": \"ETH\",\n    \"status\": true,\n    \"type\": \"SF\",\n    \"openPosition\": \"0.0188\",\n    \"disableOpen\": true,\n    \"closePosition\": \"0.0033\",\n    \"disableClose\": false,\n    \"maxTradeUSDT\": \"1200\",\n    \"leverage\": \"1\",\n    \"buyEx\": \"binance\",\n    \"sellEx\": \"binance\",\n    \"startTime\": \"0\",\n    \"minNotional\": \"15\",\n    \"maxNotional\": \"40\"\n  }\n}\n```\n\n成功响应示例：\n\n```json\n{\n  \"code\": 0,\n  \"data\": null\n}\n```\n\n失败示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"bad pair id for update\"\n}\n```\n\n### 6.4 删除 Pair\n\n用于删除已有 `pair`。\n\n最小请求体示例：\n\n```json\n{\n  \"action\": \"delete\",\n  \"pair\": {\n    \"id\": \"Ab12Cd34Ef\"\n  }\n}\n```\n\n成功响应示例：\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"pair deleted\"\n}\n```\n\n失败示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"pair id not found for delete\"\n}\n```\n\n### 6.5 发送 Warning/Notice 文本\n\n用于向服务端发送一条文本消息。\n\n服务端收到后会自动转换为：\n\n- `warning` -> `addWarning('[uiw]-' + text)`\n- `notice` -> `addWarning('[uin]-' + text)`\n\nWarning 请求体示例：\n\n```json\n{\n  \"type\": \"warning\",\n  \"text\": \"「测试」⚠️飞书报警消息\"\n}\n```\n\nNotice 请求体示例：\n\n```json\n{\n  \"type\": \"notice\",\n  \"text\": \"「测试」飞书通知消息\"\n}\n```\n\n成功响应示例：\n\n```json\n{\n  \"code\": 0,\n  \"data\": null,\n  \"message\": \"warning accepted\"\n}\n```\n\n或：\n\n```json\n{\n  \"code\": 0,\n  \"data\": null,\n  \"message\": \"notice accepted\"\n}\n```\n\n失败示例：\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"Invalid type parameter.\"\n}\n```\n\n```json\n{\n  \"code\": -1,\n  \"message\": \"bad text param: must be a non-empty string\"\n}\n```\n\n## 7. 常见错误码与状态\n\n| HTTP 状态 | 业务码 | 场景 |\n| --- | --- | --- |\n| `200` | `0` | 请求成功 |\n| `400` | `-1` | 参数错误、字段校验失败、ID 不合法 |\n| `401` | `-1` | 缺少鉴权头、时间戳错误、签名错误、API Key 未配置 |\n| `409` | `-1` | `nonce` 重复，触发重放保护 |\n| `429` | `-1` | 触发限频：`20次/10s` |\n\n## 8. 推荐调用顺序\n\n典型流程如下：\n\n1. 调用 `list` 查询当前配置\n2. 调用 `add` 新增 `pair`\n3. 再次调用 `list` 获取新增后的 `pair.id`\n4. 调用 `update` 更新该 `pair`\n5. 调用 `delete` 删除该 `pair`\n6. 如需推送消息，调用 `sdk-send-message` 发送 `warning` 或 `notice`\n"
  },
  {
    "path": "SECURITY.md",
    "content": "### [Astro产品介绍](./README.md) \n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECURITY.md) \n\n套利策略，产品使用群 \\\nhttps://t.me/astro_discuss\n\n行情工具 \\\nhttps://pulse.astro-btc.xyz/ \\\nhttps://astro-btc.github.io/Astro-Perps/?coin=ETH\n\n实时资讯, 上新，费率调整等 \\\nhttps://t.me/astro_realtime_news\n\n--------------------------------\n## 安全无小事，列一下常规防护方法\n\n#### 服务器安全\n1. 一定不要暴漏你服务器IP地址\n2. 禁止SSH密码登录，仅支持publicKey登录\n3. 开启防火墙，仅放开22端口(SSH服务) 和 Astro服务器端口(默认8443)\n\n#### 交易所API KEY安全\n1. 一定要绑定IP地址\n2. 一定不要开通提现权限\n\n"
  },
  {
    "path": "SOCKS5_INSTALL.md",
    "content": "### SOCKS5服务 一键安装教程\n\n在ubuntu 24 或 centos 7 系统执行以下命令\n\n```\ncurl -L https://raw.githubusercontent.com/astro-btc/Astro/refs/heads/main/socks5_install.sh | sudo bash -\n```\n\n安装完成后，输出\n\n```\n╔══════════════════════════════════════════════════════════════╗\n║              SOCKS5 代理服务 安装完成!                      ║\n╚══════════════════════════════════════════════════════════════╝\n\n  服务状态:  ● 运行中\n  服务地址:  11.118.252.19\n  服务端口:  2020\n  用户名  :  sockd\n  密码    :  123\n```\n\n运行以下下命令测试连通性（输出11.118.252.19即表示成功）\n```\n  curl -x socks5h://sockd:123@11.118.252.19:2020 https://ifconfig.co\n```\n\n对我们有用的信息是 服务器地址， 端口，用户名，密码这四项\n   \n"
  },
  {
    "path": "install-in-docker.sh",
    "content": "#!/bin/bash\n\n# This script is for installing Astro in docker\n# apt-get update && apt-get install -y curl\n# cd /home/ubuntu\n# curl -L https://raw.githubusercontent.com/astro-btc/astro/refs/heads/main/install-in-docker.sh | bash -\n\n# Exit on error to ensure script stops if any command fails\nset -e\n\n# 设置英文环境确保命令输出一致\nexport LANG=C\n\n# Function to check and install required tools\ncheck_and_install_tools() {\n    echo \"----> [ASTRO-INSTALL] Checking required tools...\"\n    local tools=(\"wget\" \"unzip\")\n    local missing_tools=()\n\n    # Check which tools are missing\n    for tool in \"${tools[@]}\"; do\n        if ! command -v \"$tool\" &> /dev/null; then\n            missing_tools+=(\"$tool\")\n        fi\n    done\n\n    # Install missing tools if any\n    if [ ${#missing_tools[@]} -ne 0 ]; then\n        echo \"----> [ASTRO-INSTALL] Installing missing tools: ${missing_tools[*]}\"\n        apt-get install -y \"${missing_tools[@]}\"\n    fi\n}\n\necho \"----> [ASTRO-INSTALL] Starting Astro installation...\" > /dev/tty\n\n# Check if running with root privileges\n# This script needs to be run with sudo on Ubuntu Server\nif [ \"$EUID\" -ne 0 ]; then \n    echo \"----> [ASTRO-INSTALL] ERROR: Please run this script with sudo\"\n    exit 1\nfi\n\n# Check and install required tools\ncheck_and_install_tools\n\n# 1. Install Node.js 23.x\n# Using NodeSource repository to install the latest Node.js 23.x version\necho \"----> [ASTRO-INSTALL] Installing Node.js 23.x...\"\ncurl -sL https://deb.nodesource.com/setup_23.x | bash -\napt-get install --allow-downgrades -y nodejs=23.11.1-1nodesource1\n\n# Verify Node.js installation\nnode_version=$(node -v)\necho \"----> [ASTRO-INSTALL] Node.js version: $node_version\"\n\n# 2. Install global dependencies\n# Installing required tools for running Astro:\n# - pm2: for process management and daemon\n# - bytenode: for JavaScript compilation\n# - yarn: package manager\necho \"----> [ASTRO-INSTALL] Installing global dependencies...\"\nnpm install -g pm2 bytenode yarn\necho \"----> [ASTRO-INSTALL] Installing pm2-logrotate...\"\npm2 install pm2-logrotate\n\n# 3. Download and extract latest version\n# Using GitHub API to get the latest release download link\necho \"----> [ASTRO-INSTALL] Downloading latest version...\"\nLATEST_RELEASE_URL=$(curl -s https://api.github.com/repos/astro-btc/astro/releases/latest | grep \"browser_download_url.*zip\" | cut -d '\"' -f 4)\nRELEASE_FILENAME=$(basename \"$LATEST_RELEASE_URL\")\n\n# Download the file\nwget \"$LATEST_RELEASE_URL\"\n\n# Extract files to current directory\necho \"----> [ASTRO-INSTALL] Extracting files...\"\nunzip \"$RELEASE_FILENAME\"\n\n# Fix permissions for extracted directories\necho \"----> [ASTRO-INSTALL] Fixing file permissions...\"\nchmod -R 755 astro-core astro-server astro-admin 2>/dev/null || true\nchown -R $SUDO_USER:$SUDO_USER astro-core astro-server astro-admin 2>/dev/null || true\n\n# Clean up downloaded zip file to save space\nrm \"$RELEASE_FILENAME\"\n\n# 4. Setup astro-core\necho \"----> [ASTRO-INSTALL] Setting up astro-core...\"\n\n# Enter project directory\ncd astro-core || exit 1\n\n# Install dependencies excluding better-sqlite3\necho \"----> [ASTRO-INSTALL] Installing astro-core dependencies...\"\nyarn install\n\npm2 start pm2.config.js\necho \"----> [ASTRO-INSTALL] astro-core setup completed\"\n\n# 5. Configure astro-server\necho \"----> [ASTRO-INSTALL] Configuring astro-server...\"\ncd ../astro-server || exit 1\n\nSERVER_IP=\"127.0.0.1\"\n\n# Update .env file with the IP address\nif [ -f .env ]; then\n    sed -i \"s/ALLOWED_DOMAIN=.*/ALLOWED_DOMAIN=$SERVER_IP/\" .env\n    echo \"----> [ASTRO-INSTALL] Updated .env file with IP: $SERVER_IP\"\nelse\n    echo \"----> [ASTRO-INSTALL] ERROR: .env file not found in astro-server directory\"\n    exit 1\nfi\n\n# Install dependencies and start server\necho \"----> [ASTRO-INSTALL] Installing astro-server dependencies and starting service...\"\nyarn\npm2 start pm2.config.js\n\n# 6. Setup PM2 startup\necho \"----> [ASTRO-INSTALL] Setting up PM2 startup...\"\n# pm2 startup\npm2 save\nrm -rf ../__MACOSX\n\necho \"----> [ASTRO-INSTALL] Installation completed!\"\necho \"----> [ASTRO-INSTALL] 打开浏览器访问: https://$SERVER_IP:12345/change-after-install/\"\necho \"----> [ASTRO-INSTALL] 默认密码：Astro321@\"\necho \"----> [ASTRO-INSTALL] 默认Google二次认证码：GRY5ZVAXTSYZFXFUSP7BH5QEYTEMZU42 （需要手动导入Google Authenticator）\"\n"
  },
  {
    "path": "install-with-docker.sh",
    "content": "#!/bin/bash\n\n# Astro 一键安装脚本\n# 支持的系统: 主流Linux发行版\n\nset -e\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\n# 日志函数\nlog_info() {\n    echo -e \"${GREEN}[ASTRO-INSTALL]${NC} $1\"\n}\n\nlog_warn() {\n    echo -e \"${YELLOW}[ASTRO-INSTALL]${NC} $1\"\n}\n\nlog_error() {\n    echo -e \"${RED}[ASTRO-INSTALL]${NC} $1\"\n}\n\n# 检查是否为root用户\ncheck_root() {\n    if [ \"$EUID\" -ne 0 ]; then\n        log_error \"此脚本需要root权限运行，请使用 sudo 执行\"\n        exit 1\n    fi\n}\n\n# 检查CPU架构\ncheck_architecture() {\n    log_info \"检查CPU架构...\"\n    \n    ARCH=$(uname -m)\n    case $ARCH in\n        x86_64|amd64)\n            log_info \"CPU架构: $ARCH ✓\"\n            ;;\n        *)\n            log_error \"不支持的CPU架构: $ARCH\"\n            log_error \"此脚本仅支持 x86-64 架构\"\n            exit 1\n            ;;\n    esac\n}\n\n# 检查操作系统\ncheck_os() {\n    log_info \"检查操作系统...\"\n    \n    if [ -f /etc/os-release ]; then\n        . /etc/os-release\n        OS=$NAME\n        VERSION=$VERSION_ID\n        log_info \"操作系统: $OS $VERSION\"\n        \n        # 检查是否为支持的Linux发行版\n        case $ID in\n            ubuntu|debian|centos|rhel|fedora|opensuse|sles|amzn)\n                log_info \"支持的Linux发行版 ✓\"\n                ;;\n            *)\n                log_warn \"未经测试的Linux发行版: $ID\"\n                log_warn \"脚本将继续运行，但可能遇到问题\"\n                ;;\n        esac\n    else\n        log_error \"无法识别操作系统\"\n        exit 1\n    fi\n}\n\n# 检查Docker是否已安装\ncheck_docker() {\n    log_info \"检查Docker安装状态...\"\n    \n    if command -v docker &> /dev/null; then\n        log_info \"Docker已安装\"\n        \n        # 检查Docker服务状态\n        if systemctl is-active --quiet docker; then\n            log_info \"Docker服务正在运行 ✓\"\n        else\n            log_info \"启动Docker服务...\"\n            systemctl start docker\n            systemctl enable docker\n        fi\n        \n        # 检查Docker权限\n        if docker info &> /dev/null; then\n            log_info \"Docker权限正常 ✓\"\n        else\n            log_error \"Docker权限检查失败\"\n            exit 1\n        fi\n    else\n        log_warn \"Docker未安装，开始安装...\"\n        install_docker\n    fi\n}\n\n# 安装Docker\ninstall_docker() {\n    log_info \"开始安装Docker...\"\n    \n    # 更新包管理器\n    if command -v apt-get &> /dev/null; then\n        # Ubuntu/Debian\n        apt-get update\n        apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release\n        \n        # 添加Docker官方GPG密钥\n        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n        \n        # 添加Docker APT仓库\n        echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null\n        \n        # 安装Docker\n        apt-get update\n        apt-get install -y docker-ce docker-ce-cli containerd.io\n        \n    elif command -v yum &> /dev/null; then\n        # CentOS/RHEL/Amazon Linux\n        yum install -y yum-utils\n        yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo\n        yum install -y docker-ce docker-ce-cli containerd.io\n        \n    elif command -v dnf &> /dev/null; then\n        # Fedora\n        dnf -y install dnf-plugins-core\n        dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo\n        dnf install -y docker-ce docker-ce-cli containerd.io\n        \n    else\n        log_error \"不支持的包管理器，请手动安装Docker\"\n        exit 1\n    fi\n    \n    # 启动Docker服务\n    systemctl start docker\n    systemctl enable docker\n    \n    # 确保Docker服务开机自启\n    systemctl daemon-reload\n    \n    log_info \"Docker安装完成 ✓\"\n}\n\n# 生成随机字符串\ngenerate_random_string() {\n    local length=$1\n    local chars=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\"\n    local result=\"\"\n    \n    for i in $(seq 1 $length); do\n        result=\"${result}${chars:RANDOM%${#chars}:1}\"\n    done\n    \n    echo \"$result\"\n}\n\n# 生成Google Authenticator密钥\ngenerate_2fa_secret() {\n    # 生成真正的Base32编码密钥\n    # Base32字符集: A-Z, 2-7 (共32个字符)\n    local base32_chars=\"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\"\n    local secret=\"\"\n    \n    # 生成32位Base32密钥\n    for i in $(seq 1 32); do\n        secret=\"${secret}${base32_chars:RANDOM%32:1}\"\n    done\n    \n    echo \"$secret\"\n}\n\n# IP验证函数\nvalidate_ip() {\n    local ip=$1\n    \n    # Check if empty\n    if [ -z \"$ip\" ]; then\n        return 1\n    fi\n    \n    # Check basic format: should have exactly 3 dots\n    if [ \"$(echo \"$ip\" | tr -cd '.' | wc -c)\" -ne 3 ]; then\n        return 1\n    fi\n    \n    # Split IP into parts and validate each part\n    IFS='.' read -r part1 part2 part3 part4 <<< \"$ip\"\n    \n    # Check each part is a number between 0-255\n    for part in \"$part1\" \"$part2\" \"$part3\" \"$part4\"; do\n        # Check if part is numeric\n        if ! [[ \"$part\" =~ ^[0-9]+$ ]]; then\n            return 1\n        fi\n        # Check range 0-255\n        if [ \"$part\" -lt 0 ] || [ \"$part\" -gt 255 ]; then\n            return 1\n        fi\n        # Check no leading zeros (except for \"0\")\n        if [ \"${#part}\" -gt 1 ] && [ \"${part:0:1}\" = \"0\" ]; then\n            return 1\n        fi\n    done\n    \n    return 0\n}\n\n# 可靠的IP获取函数\nget_server_ip() {\n    # 尝试自动获取公网IP\n    auto_ip=$(curl -s https://api.ipify.org || true)\n    \n    if validate_ip \"$auto_ip\"; then\n        echo \"----> [ASTRO-INSTALL] Detected public IP: $auto_ip\" > /dev/tty\n        \n        # 询问是否使用自动获取的IP\n        read -p $'\\n----> [ASTRO-INSTALL] Use this IP? [Y/n] ' confirm < /dev/tty\n        if [[ -z \"$confirm\" || \"$confirm\" =~ ^[Yy] ]]; then\n            SERVER_IP=\"$auto_ip\"\n            return\n        fi\n    fi\n    \n    # 手动输入\n    while true; do\n        echo -e \"\\n----> [ASTRO-INSTALL] Please enter your server's public IP address\" > /dev/tty\n        read -p \"IP: \" SERVER_IP < /dev/tty\n        \n        if validate_ip \"$SERVER_IP\"; then\n            break\n        else\n            echo \"ERROR: Invalid IP format (e.g. 192.168.1.1)\" > /dev/tty\n        fi\n    done\n}\n\n# 安装二维码生成工具\ninstall_qrencode() {\n    log_info \"安装二维码生成工具...\"\n    \n    if command -v qrencode &> /dev/null; then\n        log_info \"qrencode已安装 ✓\"\n        return\n    fi\n    \n    if command -v apt-get &> /dev/null; then\n        apt-get install -y qrencode\n    elif command -v yum &> /dev/null; then\n        yum install -y qrencode\n    elif command -v dnf &> /dev/null; then\n        dnf install -y qrencode\n    else\n        log_warn \"无法自动安装qrencode，请手动安装以显示二维码\"\n        return\n    fi\n    \n    log_info \"qrencode安装完成 ✓\"\n}\n\n# 生成并显示二维码\nshow_2fa_qrcode() {\n    local secret=$1\n    local ip=$2\n    \n    # Google Authenticator URI格式\n    local uri=\"otpauth://totp/?secret=${secret}&issuer=Astro\"\n    \n    if command -v qrencode &> /dev/null; then\n        qrencode -t ANSI \"${uri}\"\n        echo \"\"\n        log_info \"方法一: 请使用Google Authenticator扫描上方二维码\"\n    else\n        log_warn \"未安装qrencode，无法显示二维码\"\n        log_info \"请手动添加密钥到Google Authenticator\"\n    fi\n    \n    log_info \"方法二: 手动输入密钥，步骤：\"\n    echo -e \"   1. 打开Google Authenticator应用\"\n    echo -e \"   2. 点击 '+' 按钮\"\n    echo -e \"   3. 选择 '输入提供的密钥'\"\n    echo -e \"   4. 输入密钥: ${GREEN}${secret}${NC}\"\n    echo -e \"   5. 选择 '基于时间' 类型\"\n    echo -e \"   6. 点击 '添加' 完成设置\"\n    echo \"\"\n}\n\n# 验证重启配置\nverify_restart_configuration() {\n    log_info \"验证重启配置...\"\n    \n    # 检查Docker服务是否启用\n    if systemctl is-enabled docker &>/dev/null; then\n        log_info \"Docker服务已设置为开机自启 ✓\"\n    else\n        log_warn \"Docker服务未设置为开机自启，正在修复...\"\n        systemctl enable docker\n    fi\n    \n    # 检查容器重启策略\n    restart_policy=$(docker inspect astro-app --format='{{.HostConfig.RestartPolicy.Name}}' 2>/dev/null)\n    if [ \"$restart_policy\" = \"always\" ]; then\n        log_info \"容器重启策略已设置为 always ✓\"\n    else\n        log_warn \"容器重启策略异常: $restart_policy\"\n    fi\n    \n    # 验证容器健康检查\n    if docker inspect astro-app --format='{{.Config.Healthcheck.Test}}' 2>/dev/null | grep -q \"pm2\"; then\n        log_info \"容器健康检查已配置 ✓\"\n    else\n        log_warn \"容器健康检查未正确配置\"\n    fi\n    \n    # 验证配置文件\n    if [ -f \"astro-server/.env\" ]; then\n        log_info \"配置文件已创建 ✓\"\n        log_info \"配置文件位置: $(pwd)/astro-server/.env\"\n    else\n        log_warn \"配置文件未找到\"\n    fi\n    \n\n    \n    # 验证卷映射\n    if docker inspect astro-app --format='{{range .Mounts}}{{.Source}}:{{.Destination}}{{end}}' 2>/dev/null | grep -q \"astro-server/.env\"; then\n        log_info \"配置文件映射已配置 ✓\"\n    else\n        log_warn \"配置文件映射未正确配置\"\n    fi\n    \n    # 验证容器启动命令\n    if docker inspect astro-app --format='{{.Config.Cmd}}' 2>/dev/null | grep -q \"pm2 resurrect\"; then\n        log_info \"容器启动命令已配置PM2恢复 ✓\"\n    else\n        log_warn \"容器启动命令未包含PM2恢复逻辑\"\n    fi\n    \n    log_info \"重启配置验证完成\"\n}\n\n# 主安装函数\nmain() {\n    echo -e \"${BLUE}\"\n    echo \"╔════════════════════════════════════════════════════════════════════════════════╗\"\n    echo \"║                                                                                ║\"\n    echo \"║                          🚀 Astro 一键安装脚本 🚀                                ║\"\n    echo \"║                                                                                ║\"\n    echo \"╚════════════════════════════════════════════════════════════════════════════════╝\"\n    echo -e \"${NC}\\n\"\n    \n    log_info \"开始安装 Astro...\"\n    \n    # 系统检查\n    check_root\n    check_architecture\n    check_os\n    \n    # Docker检查和安装\n    check_docker\n    \n    # 安装二维码生成工具\n    install_qrencode\n    \n    # 获取服务器IP\n    echo \"----> [ASTRO-INSTALL] Starting Astro installation...\" > /dev/tty\n    get_server_ip\n    \n    # 生成随机配置\n    log_info \"生成安全配置...\"\n    \n    ADMIN_PREFIX=$(generate_random_string 6)\n    ADMIN_2FA_SECRET=$(generate_2fa_secret)\n    ADMIN_JWT_SECRET=$(generate_random_string 32)\n    \n    log_info \"配置生成完成 ✓\"\n    \n    # 创建配置目录\n    log_info \"创建配置目录...\"\n    mkdir -p astro-server\n    \n    # 创建.env文件\n    log_info \"生成配置文件...\"\n    cat > astro-server/.env << EOF\nPORT=12345\nALLOWED_DOMAIN=$SERVER_IP\nADMIN_PREFIX=$ADMIN_PREFIX\nADMIN_SECURITY_CODE=Astro321@\nADMIN_2FA_SECRET=$ADMIN_2FA_SECRET\nADMIN_JWT_SECRET=$ADMIN_JWT_SECRET\nADMIN_JWT_EXPIRESIN=240h\nEOF\n    \n    # 设置环境变量\n    export PORT=12345\n    export ALLOWED_DOMAIN=\"$SERVER_IP\"\n    export ADMIN_PREFIX=\"$ADMIN_PREFIX\"\n    export ADMIN_SECURITY_CODE=\"Astro321@\"\n    export ADMIN_2FA_SECRET=\"$ADMIN_2FA_SECRET\"\n    export ADMIN_JWT_SECRET=\"$ADMIN_JWT_SECRET\"\n    export ADMIN_JWT_EXPIRESIN=\"240h\"\n    \n    log_info \"配置文件已保存到: astro-server/.env\"\n    \n    # 停止并删除旧容器（如果存在）\n    log_info \"清理旧容器...\"\n    docker stop astro-app 2>/dev/null || true\n    docker rm astro-app 2>/dev/null || true\n    \n    # 拉取Docker镜像\n    log_info \"拉取Docker镜像...\"\n    docker pull mydocker788/astro:latest\n    \n    # 运行Docker容器\n    log_info \"启动Astro容器...\"\n    docker run -d \\\n        --name astro-app \\\n        --restart always \\\n        --health-cmd=\"pm2 status && pm2 list | grep -q online\" \\\n        --health-interval=30s \\\n        --health-timeout=10s \\\n        --health-retries=3 \\\n        -p 12345:12345 \\\n        -v \"$(pwd)/astro-server/.env:/home/ubuntu/astro-server/.env\" \\\n        mydocker788/astro:latest \\\n        bash -c \"\n            echo '=== 容器启动，恢复PM2进程 ==='\n            echo '时间: $(date)'\n            \n            # 等待5秒确保容器完全启动\n            sleep 5\n            \n            # 恢复PM2进程\n            echo '执行 pm2 resurrect...'\n            pm2 resurrect\n            \n            # 等待恢复完成\n            sleep 3\n            \n            # 检查PM2状态\n            echo '检查PM2状态...'\n            pm2 status\n            \n            echo '=== PM2恢复完成 ==='\n            \n            # 保持容器运行\n            tail -f /dev/null\n        \"\n    \n    # 等待容器启动\n    log_info \"等待容器启动...\"\n    sleep 5\n    \n    # 检查容器状态\n    if docker ps | grep -q astro-app; then\n        log_info \"容器启动成功 ✓\"\n    else\n        log_error \"容器启动失败\"\n        docker logs astro-app\n        exit 1\n    fi\n    \n    # 等待PM2进程恢复完成\n    log_info \"等待PM2进程恢复完成...\"\n    sleep 10\n    \n    # 检查pm2状态\n    if docker exec astro-app pm2 status &>/dev/null && docker exec astro-app pm2 list | grep -q \"online\"; then\n        log_info \"pm2进程启动成功 ✓\"\n        \n        # 显示pm2状态\n        echo -e \"\\n${BLUE}PM2 进程状态:${NC}\"\n        docker exec astro-app pm2 status\n        echo \"\"\n    else\n        log_warn \"pm2状态检查失败，查看启动日志...\"\n        echo -e \"\\n${YELLOW}=== 容器启动日志 ===${NC}\"\n        docker logs --tail 30 astro-app\n        echo \"\"\n    fi\n    \n    # 验证重启配置\n    verify_restart_configuration\n    \n    # 显示安装完成信息\n    echo -e \"\\n${GREEN}╔════════════════════════════════════════════════════════════════════════════════╗${NC}\"\n    echo -e \"${GREEN}║                                                                                ║${NC}\"\n    echo -e \"${GREEN}║                         🎉 安装完成！🎉                                         ║${NC}\"\n    echo -e \"${GREEN}║                                                                                ║${NC}\"\n    echo -e \"${GREEN}╚════════════════════════════════════════════════════════════════════════════════╝${NC}\\n\"\n    \n    echo -e \"${BLUE}📋 安装信息:${NC}\"\n    echo -e \"   🌐 访问地址: ${GREEN}https://$SERVER_IP:$PORT/$ADMIN_PREFIX${NC}\"\n    echo -e \"   🔑 密  码: ${YELLOW}$ADMIN_SECURITY_CODE${NC}\"\n    echo -e \"   📱 2FA密钥: ${YELLOW}$ADMIN_2FA_SECRET${NC}\"\n    echo -e \"   📁 配置文件: ${GREEN}$(pwd)/astro-server/.env${NC}\"\n    echo \"\"\n\n    # 显示二维码\n    show_2fa_qrcode \"$ADMIN_2FA_SECRET\" \"$SERVER_IP\"\n    \n    log_info \"Astro安装完成！感谢使用！\"\n}\n\n# 执行主函数\nmain \"$@\" \n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/bash\n\n# Astro 一键安装脚本\n# 支持的系统: 主流Linux发行版\n\nset -e\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nNC='\\033[0m' # No Color\n\n# 日志函数\nlog_info() {\n    echo -e \"${GREEN}[ASTRO-INSTALL]${NC} $1\"\n}\n\n# 验证channel参数（纯数字且长度<24字节）\nvalidate_channel_id() {\n    local ch=\"$1\"\n    if [ -z \"$ch\" ]; then\n        return 1\n    fi\n    if ! [[ \"$ch\" =~ ^[0-9]+$ ]]; then\n        return 1\n    fi\n    if [ ${#ch} -ge 24 ]; then\n        return 1\n    fi\n    return 0\n}\n\n# 从脚本参数中解析 channel（支持 --channel 123、--channel=123、-c 123、channel=123）\nparse_channel_from_args() {\n    local ch=\"\"\n    while [ $# -gt 0 ]; do\n        case \"$1\" in\n            --channel)\n                if [ $# -ge 2 ]; then\n                    ch=\"$2\"\n                    shift 2\n                    continue\n                else\n                    shift\n                    continue\n                fi\n                ;;\n            --channel=*)\n                ch=\"${1#--channel=}\"\n                shift\n                continue\n                ;;\n            -c)\n                if [ $# -ge 2 ]; then\n                    ch=\"$2\"\n                    shift 2\n                    continue\n                else\n                    shift\n                    continue\n                fi\n                ;;\n            channel=*)\n                ch=\"${1#channel=}\"\n                shift\n                continue\n                ;;\n            *)\n                shift\n                ;;\n        esac\n    done\n    echo \"$ch\"\n}\n\nlog_warn() {\n    echo -e \"${YELLOW}[ASTRO-INSTALL]${NC} $1\"\n}\n\nlog_error() {\n    echo -e \"${RED}[ASTRO-INSTALL]${NC} $1\"\n}\n\n# 检查是否为root用户\ncheck_root() {\n    if [ \"$EUID\" -ne 0 ]; then\n        log_error \"此脚本需要root权限运行，请使用 sudo 执行\"\n        exit 1\n    fi\n}\n\n# 检查CPU架构\ncheck_architecture() {\n    log_info \"检查CPU架构...\"\n    \n    ARCH=$(uname -m)\n    case $ARCH in\n        x86_64|amd64)\n            log_info \"CPU架构: $ARCH ✓\"\n            ;;\n        *)\n            log_error \"不支持的CPU架构: $ARCH\"\n            log_error \"此脚本仅支持 x86-64 架构\"\n            exit 1\n            ;;\n    esac\n}\n\n# 检查操作系统\ncheck_os() {\n    log_info \"检查操作系统...\"\n    \n    if [ -f /etc/os-release ]; then\n        . /etc/os-release\n        OS=$NAME\n        VERSION=$VERSION_ID\n        log_info \"操作系统: $OS $VERSION\"\n        \n        # 检查是否为支持的Linux发行版\n        case $ID in\n            ubuntu|debian|centos|rhel|fedora|opensuse|sles|amzn)\n                log_info \"支持的Linux发行版 ✓\"\n                ;;\n            *)\n                log_warn \"未经测试的Linux发行版: $ID\"\n                log_warn \"脚本将继续运行，但可能遇到问题\"\n                ;;\n        esac\n    else\n        log_error \"无法识别操作系统\"\n        exit 1\n    fi\n}\n\n# 检查Docker是否已安装\ncheck_docker() {\n    log_info \"检查Docker安装状态...\"\n    \n    if command -v docker &> /dev/null; then\n        log_info \"Docker已安装\"\n        \n        # 检查Docker服务状态\n        if systemctl is-active --quiet docker; then\n            log_info \"Docker服务正在运行 ✓\"\n        else\n            log_info \"启动Docker服务...\"\n            systemctl start docker\n            systemctl enable docker\n        fi\n        \n        # 检查Docker权限\n        if docker info &> /dev/null; then\n            log_info \"Docker权限正常 ✓\"\n        else\n            log_error \"Docker权限检查失败\"\n            exit 1\n        fi\n    else\n        log_warn \"Docker未安装，开始安装...\"\n        install_docker\n    fi\n}\n\n# 安装Docker\ninstall_docker() {\n    log_info \"开始安装Docker...\"\n    \n    # 更新包管理器\n    if command -v apt-get &> /dev/null; then\n        # Ubuntu/Debian\n        apt-get update\n        apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release\n        \n        # 添加Docker官方GPG密钥\n        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n        \n        # 添加Docker APT仓库\n        echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null\n        \n        # 安装Docker\n        apt-get update\n        apt-get install -y docker-ce docker-ce-cli containerd.io\n        \n    elif command -v yum &> /dev/null; then\n        # CentOS/RHEL/Amazon Linux\n        yum install -y yum-utils\n        yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo\n        yum install -y docker-ce docker-ce-cli containerd.io\n        \n    elif command -v dnf &> /dev/null; then\n        # Fedora\n        dnf -y install dnf-plugins-core\n        dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo\n        dnf install -y docker-ce docker-ce-cli containerd.io\n        \n    else\n        log_error \"不支持的包管理器，请手动安装Docker\"\n        exit 1\n    fi\n    \n    # 启动Docker服务\n    systemctl start docker\n    systemctl enable docker\n    \n    # 确保Docker服务开机自启\n    systemctl daemon-reload\n    \n    log_info \"Docker安装完成 ✓\"\n}\n\n# 生成随机字符串（基于 /dev/urandom，加密安全）\ngenerate_random_string() {\n    local length=$1\n    LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c \"$length\"\n}\n\n# 生成Google Authenticator密钥（Base32，基于 /dev/urandom）\ngenerate_2fa_secret() {\n    LC_ALL=C tr -dc 'A-Z2-7' </dev/urandom | head -c 32\n}\n\n# 生成管理员登录密码（前7位随机字母数字 + 末位 @）\ngenerate_security_code() {\n    local prefix\n    prefix=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 7)\n    echo \"${prefix}@\"\n}\n\n# IP验证函数\nvalidate_ip() {\n    local ip=$1\n    \n    # Check if empty\n    if [ -z \"$ip\" ]; then\n        return 1\n    fi\n    \n    # Check basic format: should have exactly 3 dots\n    if [ \"$(echo \"$ip\" | tr -cd '.' | wc -c)\" -ne 3 ]; then\n        return 1\n    fi\n    \n    # Split IP into parts and validate each part\n    IFS='.' read -r part1 part2 part3 part4 <<< \"$ip\"\n    \n    # Check each part is a number between 0-255\n    for part in \"$part1\" \"$part2\" \"$part3\" \"$part4\"; do\n        # Check if part is numeric\n        if ! [[ \"$part\" =~ ^[0-9]+$ ]]; then\n            return 1\n        fi\n        # Check range 0-255\n        if [ \"$part\" -lt 0 ] || [ \"$part\" -gt 255 ]; then\n            return 1\n        fi\n        # Check no leading zeros (except for \"0\")\n        if [ \"${#part}\" -gt 1 ] && [ \"${part:0:1}\" = \"0\" ]; then\n            return 1\n        fi\n    done\n    \n    return 0\n}\n\n# 可靠的IP获取函数\nget_server_ip() {\n    # 尝试自动获取公网IP\n    auto_ip=$(curl -s https://api.ipify.org || true)\n    \n    if validate_ip \"$auto_ip\"; then\n        echo \"----> [ASTRO-INSTALL] Detected public IP: $auto_ip\" > /dev/tty\n        \n        # 询问是否使用自动获取的IP\n        read -p $'\\n----> [ASTRO-INSTALL] Use this IP? [Y/n] ' confirm < /dev/tty\n        if [[ -z \"$confirm\" || \"$confirm\" =~ ^[Yy] ]]; then\n            SERVER_IP=\"$auto_ip\"\n            return\n        fi\n    fi\n    \n    # 手动输入\n    while true; do\n        echo -e \"\\n----> [ASTRO-INSTALL] Please enter your server's public IP address\" > /dev/tty\n        read -p \"IP: \" SERVER_IP < /dev/tty\n        \n        if validate_ip \"$SERVER_IP\"; then\n            break\n        else\n            echo \"ERROR: Invalid IP format (e.g. 192.168.1.1)\" > /dev/tty\n        fi\n    done\n}\n\n# 安装二维码生成工具\ninstall_qrencode() {\n    log_info \"安装二维码生成工具...\"\n    \n    if command -v qrencode &> /dev/null; then\n        log_info \"qrencode已安装 ✓\"\n        return\n    fi\n    \n    if command -v apt-get &> /dev/null; then\n        apt-get install -y qrencode\n    elif command -v yum &> /dev/null; then\n        yum install -y qrencode\n    elif command -v dnf &> /dev/null; then\n        dnf install -y qrencode\n    else\n        log_warn \"无法自动安装qrencode，请手动安装以显示二维码\"\n        return\n    fi\n    \n    log_info \"qrencode安装完成 ✓\"\n}\n\n# 生成并显示二维码\nshow_2fa_qrcode() {\n    local secret=$1\n    local ip=$2\n    \n    # Google Authenticator URI格式\n    local uri=\"otpauth://totp/?secret=${secret}&issuer=Astro\"\n    \n    if command -v qrencode &> /dev/null; then\n        qrencode -t ANSI \"${uri}\"\n        echo \"\"\n        log_info \"方法一: 请使用Google Authenticator扫描上方二维码\"\n    else\n        log_warn \"未安装qrencode，无法显示二维码\"\n        log_info \"请手动添加密钥到Google Authenticator\"\n    fi\n    \n    log_info \"方法二: 手动输入密钥，步骤：\"\n    echo -e \"   1. 打开Google Authenticator应用\"\n    echo -e \"   2. 点击 '+' 按钮\"\n    echo -e \"   3. 选择 '输入提供的密钥'\"\n    echo -e \"   4. 输入密钥: ${GREEN}${secret}${NC}\"\n    echo -e \"   5. 选择 '基于时间' 类型\"\n    echo -e \"   6. 点击 '添加' 完成设置\"\n    echo \"\"\n}\n\n# 验证重启配置\nverify_restart_configuration() {\n    log_info \"验证重启配置...\"\n    \n    # 检查Docker服务是否启用\n    if systemctl is-enabled docker &>/dev/null; then\n        log_info \"Docker服务已设置为开机自启 ✓\"\n    else\n        log_warn \"Docker服务未设置为开机自启，正在修复...\"\n        systemctl enable docker\n    fi\n    \n    # 检查容器重启策略\n    restart_policy=$(docker inspect astro-app --format='{{.HostConfig.RestartPolicy.Name}}' 2>/dev/null)\n    if [ \"$restart_policy\" = \"always\" ]; then\n        log_info \"容器重启策略已设置为 always ✓\"\n    else\n        log_warn \"容器重启策略异常: $restart_policy\"\n    fi\n    \n    # 验证容器健康检查\n    if docker inspect astro-app --format='{{.Config.Healthcheck.Test}}' 2>/dev/null | grep -q \"pm2\"; then\n        log_info \"容器健康检查已配置 ✓\"\n    else\n        log_warn \"容器健康检查未正确配置\"\n    fi\n    \n    # 验证配置文件\n    if [ -f \"astro-server/.env\" ]; then\n        log_info \"配置文件已创建 ✓\"\n        log_info \"配置文件位置: $(pwd)/astro-server/.env\"\n    else\n        log_warn \"配置文件未找到\"\n    fi\n    \n\n    \n    # 验证卷映射\n    if docker inspect astro-app --format='{{range .Mounts}}{{.Source}}:{{.Destination}}{{end}}' 2>/dev/null | grep -q \"astro-server/.env\"; then\n        log_info \"配置文件映射已配置 ✓\"\n    else\n        log_warn \"配置文件映射未正确配置\"\n    fi\n    \n    # 验证容器启动命令\n    if docker inspect astro-app --format='{{.Config.Cmd}}' 2>/dev/null | grep -q \"pm2 resurrect\"; then\n        log_info \"容器启动命令已配置PM2恢复 ✓\"\n    else\n        log_warn \"容器启动命令未包含PM2恢复逻辑\"\n    fi\n    \n    log_info \"重启配置验证完成\"\n}\n\n# 主安装函数\nmain() {\n    echo -e \"${BLUE}\"\n    echo \"╔════════════════════════════════════════════════════════════════════════════════╗\"\n    echo \"║                                                                                ║\"\n    echo \"║                          🚀 Astro 一键安装脚本 🚀                                ║\"\n    echo \"║                                                                                ║\"\n    echo \"╚════════════════════════════════════════════════════════════════════════════════╝\"\n    echo -e \"${NC}\\n\"\n    \n    log_info \"开始安装 Astro...\"\n    \n    # 系统检查\n    check_root\n    check_architecture\n    check_os\n    \n    # Docker检查和安装\n    check_docker\n    \n    # 安装二维码生成工具\n    install_qrencode\n    \n    # 获取服务器IP\n    echo \"----> [ASTRO-INSTALL] Starting Astro installation...\" > /dev/tty\n    get_server_ip\n    \n    # 解析 channel 参数（优先脚本参数，其次环境变量）\n    CHANNEL_ID=\"\"\n    _arg_channel=\"$(parse_channel_from_args \"$@\" || true)\"\n    if validate_channel_id \"$_arg_channel\"; then\n        CHANNEL_ID=\"$_arg_channel\"\n        log_info \"检测到 channel 参数: $CHANNEL_ID\"\n    elif validate_channel_id \"$CHANNEL_ID\"; then\n        # 已存在且有效的环境变量 CHANNEL_ID\n        log_info \"检测到环境变量 CHANNEL_ID: $CHANNEL_ID\"\n    else\n        log_info \"未检测到有效的 channel 参数，将使用空字符串\"\n        CHANNEL_ID=\"\"\n    fi\n    \n    # 生成随机配置\n    log_info \"生成安全配置...\"\n    \n    ADMIN_PREFIX=$(generate_random_string 10)\n    ADMIN_2FA_SECRET=$(generate_2fa_secret)\n    ADMIN_JWT_SECRET=$(generate_random_string 32)\n    ADMIN_SECURITY_CODE=$(generate_security_code)\n    \n    log_info \"配置生成完成 ✓\"\n    \n    # 创建配置目录\n    log_info \"创建配置目录...\"\n    mkdir -p astro-server\n    \n    # 创建.env文件\n    log_info \"生成配置文件...\"\n    cat > astro-server/.env << EOF\nPORT=8443\nALLOWED_DOMAIN=$SERVER_IP\nADMIN_PREFIX=$ADMIN_PREFIX\nADMIN_SECURITY_CODE=$ADMIN_SECURITY_CODE\nADMIN_2FA_SECRET=$ADMIN_2FA_SECRET\nADMIN_JWT_SECRET=$ADMIN_JWT_SECRET\nADMIN_JWT_EXPIRESIN=240h\nCHANNEL_ID=$CHANNEL_ID\nEOF\n    \n    # 设置环境变量\n    export PORT=8443\n    export ALLOWED_DOMAIN=\"$SERVER_IP\"\n    export ADMIN_PREFIX=\"$ADMIN_PREFIX\"\n    export ADMIN_SECURITY_CODE=\"$ADMIN_SECURITY_CODE\"\n    export ADMIN_2FA_SECRET=\"$ADMIN_2FA_SECRET\"\n    export ADMIN_JWT_SECRET=\"$ADMIN_JWT_SECRET\"\n    export ADMIN_JWT_EXPIRESIN=\"240h\"\n    export CHANNEL_ID=\"$CHANNEL_ID\"\n    \n    log_info \"配置文件已保存到: astro-server/.env\"\n    \n    # 停止并删除旧容器（如果存在）\n    log_info \"清理旧容器...\"\n    docker stop astro-app 2>/dev/null || true\n    docker rm astro-app 2>/dev/null || true\n    \n    # 拉取Docker镜像\n    log_info \"拉取Docker镜像...\"\n    docker pull astrobtc/astro:latest\n    \n    # 运行Docker容器\n    log_info \"启动Astro容器...\"\n    docker run -d \\\n        --name astro-app \\\n        --restart always \\\n        --health-cmd=\"pm2 status && pm2 list | grep -q online\" \\\n        --health-interval=30s \\\n        --health-timeout=10s \\\n        --health-retries=3 \\\n        -p 8443:8443 \\\n        -v \"$(pwd)/astro-server/.env:/home/ubuntu/astro-server/.env\" \\\n        astrobtc/astro:latest \\\n        bash -c \"\n            echo '=== 容器启动，恢复PM2进程 ==='\n            echo '时间: $(date)'\n            \n            # 等待5秒确保容器完全启动\n            sleep 5\n            \n            # 恢复PM2进程\n            echo '执行 pm2 resurrect...'\n            pm2 resurrect\n            \n            # 等待恢复完成\n            sleep 3\n            \n            # 检查PM2状态\n            echo '检查PM2状态...'\n            pm2 status\n            \n            echo '=== PM2恢复完成 ==='\n            \n            # 保持容器运行\n            tail -f /dev/null\n        \"\n    \n    # 等待容器启动\n    log_info \"等待容器启动...\"\n    sleep 5\n    \n    # 检查容器状态\n    if docker ps | grep -q astro-app; then\n        log_info \"容器启动成功 ✓\"\n    else\n        log_error \"容器启动失败\"\n        docker logs astro-app\n        exit 1\n    fi\n    \n    # 等待PM2进程恢复完成\n    log_info \"等待PM2进程恢复完成...\"\n    sleep 10\n    \n    # 检查pm2状态\n    if docker exec astro-app pm2 status &>/dev/null && docker exec astro-app pm2 list | grep -q \"online\"; then\n        log_info \"pm2进程启动成功 ✓\"\n        \n        # 显示pm2状态\n        echo -e \"\\n${BLUE}PM2 进程状态:${NC}\"\n        docker exec astro-app pm2 status\n        echo \"\"\n    else\n        log_warn \"pm2状态检查失败，查看启动日志...\"\n        echo -e \"\\n${YELLOW}=== 容器启动日志 ===${NC}\"\n        docker logs --tail 30 astro-app\n        echo \"\"\n    fi\n    \n    # 验证重启配置\n    verify_restart_configuration\n    \n    # 显示安装完成信息\n    echo -e \"\\n${GREEN}╔════════════════════════════════════════════════════════════════════════════════╗${NC}\"\n    echo -e \"${GREEN}║                                                                                ║${NC}\"\n    echo -e \"${GREEN}║                         🎉 安装完成！🎉                                         ║${NC}\"\n    echo -e \"${GREEN}║                                                                                ║${NC}\"\n    echo -e \"${GREEN}╚════════════════════════════════════════════════════════════════════════════════╝${NC}\\n\"\n    \n    echo -e \"${BLUE}📋 安装信息:${NC}\"\n    echo -e \"   🌐 访问地址: ${GREEN}https://$SERVER_IP:$PORT/$ADMIN_PREFIX${NC}\"\n    echo -e \"   🔑 密  码: ${YELLOW}$ADMIN_SECURITY_CODE${NC}\"\n    echo -e \"   📱 2FA密钥: ${YELLOW}$ADMIN_2FA_SECRET${NC}\"\n    echo -e \"   📁 配置文件: ${GREEN}$(pwd)/astro-server/.env${NC}\"\n    echo \"\"\n\n    # 显示二维码\n    show_2fa_qrcode \"$ADMIN_2FA_SECRET\" \"$SERVER_IP\"\n    \n    log_info \"Astro安装完成！感谢使用！\"\n}\n\n# 执行主函数\nmain \"$@\" \n"
  },
  {
    "path": "sdk-demo.js",
    "content": "const crypto = require('crypto')\nconst http = require('http');\nconst https = require('https');\n\nconst ACTIONS_WITH_PAIR = new Set(['add', 'update', 'delete'])\nconst MESSAGE_TYPES = new Set(['warning', 'notice'])\n\nfunction generateNonce() {\n    return crypto.randomBytes(24).toString('base64')\n        .replace(/\\+/g, '-')\n        .replace(/\\//g, '_')\n        .replace(/=/g, '')\n        .slice(0, 32);\n}\n\nfunction sleep(ms) {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nconst buildSimplePayload = (action, pair) => {\n    const payload = { action }\n    if (ACTIONS_WITH_PAIR.has(action)) {\n        payload.pair = pair\n    }\n    return payload\n}\n\nconst buildMessagePayload = (type, text) => {\n    return {\n        type,\n        text\n    }\n}\n\nconst buildCanonicalMessage = ({ method = 'POST', path = '', nonce, ts, rawBody = '' }) => {\n    return [\n        String(ts),\n        String(nonce),\n        String(method).toUpperCase(),\n        String(path),\n        typeof rawBody === 'string' ? rawBody : ''\n    ].join('\\n')\n}\n\nconst simpleSignRawBody = (key, nonce, ts, path, rawBody, method = 'POST') => {\n    const signContent = buildCanonicalMessage({ method, path, nonce, ts, rawBody })\n    return crypto.createHmac('sha256', String(key))\n        .update(signContent)\n        .digest('hex');\n}\n\n// ============ 配置 ============\n// API Key - 需要先在账户->开发者代码通过「set api-key ***」设置API KEY, 随机数长度12-32，数字和大小写字母\n// ⚠️ 如果api key被黑客盗取，黑客就可以开单了，切记谨慎保管，定期更换，长度尽量长\nconst API_KEY = process.env.ASTRO_API_KEY || '***'; // 请替换为实际的 API Key\n\n// 服务器地址\nconst BASE_URL = '127.0.0.1'; // 请替换为实际的服务器地址\nconst PORT = 8443;\nconst PAIR_API_PATH = '/api/config/sdk-update-pair';\nconst MESSAGE_API_PATH = '/api/config/sdk-send-message';\n\n// 默认使用 HTTPS\nconst USE_HTTPS = true;\nconst PROTOCOL = USE_HTTPS ? 'https' : 'http';\nconst HTTP_MODULE = USE_HTTPS ? https : http;\n\n// 忽略 SSL 证书验证\nif (USE_HTTPS) {\n    process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';\n}\n\n// ============ Pair 模板 ============\nconst pair = {\n    \"name\": \"ETH\",\n    \"status\": false,\n    \"type\": \"SF\",\n    \"openPosition\": \"0.0158\",\n    \"disableOpen\": false,\n    \"closePosition\": \"0.0022\",\n    \"disableClose\": false,\n    \"maxTradeUSDT\": \"1000\",\n    \"leverage\": \"1\",\n    \"buyEx\": \"binance\",\n    \"sellEx\": \"binance\",\n    \"startTime\": \"0\",\n    \"minNotional\": \"12\",\n    \"maxNotional\": \"30\",\n};\n\nconst messageDemoPayload = {\n    warning: '「测试」⚠️ASTRO SDK API报警消息',\n    notice: '「测试」ASTRO SDK API通知消息'\n}\n\nfunction createUpdatedPair(addedPair) {\n    return {\n        ...addedPair,\n        status: true,\n        disableOpen: true,\n        maxTradeUSDT: '1200',\n        minNotional: '15',\n        maxNotional: '40',\n        openPosition: '0.0188',\n        closePosition: '0.0033'\n    };\n}\n\nfunction printDivider(title) {\n    console.log('\\n' + '='.repeat(28));\n    console.log(title);\n    console.log('='.repeat(28));\n}\n\nfunction printJson(title, value) {\n    console.log(title);\n    console.log(JSON.stringify(value, null, 2));\n}\n\nfunction summarizePair(targetPair) {\n    if (!targetPair) {\n        return null\n    }\n    return {\n        id: targetPair.id,\n        name: targetPair.name,\n        type: targetPair.type,\n        status: targetPair.status,\n        disableOpen: targetPair.disableOpen,\n        openPosition: targetPair.openPosition,\n        closePosition: targetPair.closePosition,\n        maxTradeUSDT: targetPair.maxTradeUSDT,\n        minNotional: targetPair.minNotional,\n        maxNotional: targetPair.maxNotional,\n        buyEx: targetPair.buyEx,\n        sellEx: targetPair.sellEx\n    }\n}\n\nfunction assertSuccess(stepName, response) {\n    if (response.statusCode !== 200 || response.json?.code !== 0) {\n        throw new Error(`${stepName} 失败，HTTP=${response.statusCode}，响应=${response.rawBody}`);\n    }\n}\n\nfunction findPairByName(list, name) {\n    return Array.isArray(list) ? list.find(item => item?.name === name) : null\n}\n\nfunction findPairById(list, id) {\n    return Array.isArray(list) ? list.find(item => item?.id === id) : null\n}\n\nfunction buildLogSafeHeaders(headers) {\n    return {\n        'Content-Type': headers['Content-Type'],\n        'x-timestamp': headers['x-timestamp'],\n        'Content-Length': headers['Content-Length']\n    }\n}\n\nfunction sendSignedRequest(apiPath, payload, stepLabel, summary = {}) {\n    const timestamp = Date.now();\n    const nonce = generateNonce();\n    const requestBody = JSON.stringify(payload);\n    const sign = simpleSignRawBody(API_KEY, nonce, timestamp, apiPath, requestBody, 'POST');\n\n    const headers = {\n        'Content-Type': 'application/json',\n        'x-timestamp': timestamp.toString(),\n        'x-sign': sign,\n        'x-nonce': nonce,\n        'Content-Length': Buffer.byteLength(requestBody)\n    };\n\n    const options = {\n        hostname: BASE_URL,\n        port: PORT,\n        path: apiPath,\n        method: 'POST',\n        headers\n    };\n\n    printDivider(`[${stepLabel}] 请求开始`);\n    console.log('Protocol:', PROTOCOL.toUpperCase());\n    console.log('URL:', `${PROTOCOL}://${BASE_URL}:${PORT}${apiPath}`);\n    if (summary.action) {\n        console.log('Action:', summary.action);\n    }\n    if (summary.type) {\n        console.log('Type:', summary.type);\n    }\n    console.log('Timestamp:', timestamp);\n    printJson('Headers:', buildLogSafeHeaders(headers));\n    printJson('Body:', payload);\n\n    return new Promise((resolve, reject) => {\n        const req = HTTP_MODULE.request(options, (res) => {\n            let data = '';\n\n            res.on('data', (chunk) => {\n                data += chunk;\n            });\n\n            res.on('end', () => {\n                let json = null\n                try {\n                    json = JSON.parse(data);\n                } catch (e) {\n                }\n\n                printDivider(`[${stepLabel}] 响应完成`);\n                console.log('Status Code:', res.statusCode);\n                console.log('Status Message:', res.statusMessage);\n                printJson('Response Headers:', res.headers);\n                if (json) {\n                    printJson('Response JSON:', json);\n                } else {\n                    console.log('Response Body (raw):');\n                    console.log(data);\n                }\n\n                resolve({\n                    statusCode: res.statusCode,\n                    statusMessage: res.statusMessage,\n                    headers: res.headers,\n                    json,\n                    rawBody: data\n                });\n            });\n        });\n\n        req.on('error', (error) => {\n            printDivider(`[${stepLabel}] 请求错误`);\n            console.error('Error:', error.message);\n            if (error.code === 'ECONNREFUSED') {\n                console.error('提示: 无法连接到服务器，请确保服务器正在运行');\n            } else if (USE_HTTPS && (error.code === 'CERT_HAS_EXPIRED' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE')) {\n                console.error('提示: SSL 证书验证失败，已设置忽略证书验证');\n            }\n            reject(error);\n        });\n\n        req.write(requestBody);\n        req.end();\n    });\n}\n\nfunction sendRequest(action, pairData, stepLabel) {\n    const payload = buildSimplePayload(action, pairData);\n    return sendSignedRequest(PAIR_API_PATH, payload, stepLabel, { action })\n}\n\nfunction sendMessageRequest(type, text, stepLabel) {\n    if (!MESSAGE_TYPES.has(type)) {\n        throw new Error(`无效的消息类型: ${type}`);\n    }\n    if (typeof text !== 'string' || !text.trim()) {\n        throw new Error('text 必须是非空字符串');\n    }\n\n    const payload = buildMessagePayload(type, text);\n    return sendSignedRequest(MESSAGE_API_PATH, payload, stepLabel, { type })\n}\n\nasync function runDemoFlow() {\n    printDivider('Pair Demo Flow 启动');\n    console.log(`目标接口: ${PROTOCOL}://${BASE_URL}:${PORT}${PAIR_API_PATH}`);\n\n    printJson('Add Pair 模板:', summarizePair(pair));\n\n    const listBefore = await sendRequest('list', null, 'Step 1 - list');\n    assertSuccess('Step 1 - list', listBefore);\n    const beforeList = listBefore.json?.data || [];\n    console.log('Step 1 结果: 当前总 pair 数量 =', beforeList.length);\n    const beforeExisting = findPairByName(beforeList, pair.name);\n    if (beforeExisting) {\n        throw new Error(`开始前已存在同名 pair，请稍后重试或修改名称: ${pair.name}`);\n    }\n\n    const addResult = await sendRequest('add', pair, 'Step 2 - add');\n    assertSuccess('Step 2 - add', addResult);\n    console.log('Step 2 结果: add 请求已提交成功');\n\n    // 新增pair会导致astro-core进程重启，因此此动作完成后需要等待3秒再执行其他动作。\n    await sleep(4000);\n\n    const listAfterAdd = await sendRequest('list', null, 'Step 3 - list');\n    assertSuccess('Step 3 - list', listAfterAdd);\n    const afterAddList = listAfterAdd.json?.data || [];\n    console.log('Step 3 结果: 当前总 pair 数量 =', afterAddList.length);\n    const addedPair = findPairByName(afterAddList, pair.name);\n    if (!addedPair) {\n        throw new Error('Step 3 - list 未找到刚刚新增的 pair，无法继续 update/delete 流程');\n    }\n    printJson('Step 3 找到新增 pair:', summarizePair(addedPair));\n\n    const pairForUpdate = createUpdatedPair(addedPair);\n    printJson('Step 4 将要更新为:', summarizePair(pairForUpdate));\n    const updateResult = await sendRequest('update', pairForUpdate, 'Step 4 - updatePair');\n    assertSuccess('Step 4 - updatePair', updateResult);\n    console.log('Step 4 结果: update 请求已提交成功');\n\n    const listAfterUpdate = await sendRequest('list', null, 'Step 5 - list');\n    assertSuccess('Step 5 - list', listAfterUpdate);\n    const afterUpdateList = listAfterUpdate.json?.data || [];\n    const updatedPair = findPairById(afterUpdateList, addedPair.id);\n    if (!updatedPair) {\n        throw new Error(`Step 5 - list 未找到 id=${addedPair.id} 的 pair`);\n    }\n    printJson('Step 5 更新后的 pair:', summarizePair(updatedPair));\n\n    const deletePayload = { id: addedPair.id };\n    printJson('Step 6 删除参数:', deletePayload);\n    const deleteResult = await sendRequest('delete', deletePayload, 'Step 6 - delete');\n    assertSuccess('Step 6 - delete', deleteResult);\n    console.log('Step 6 结果: delete 请求已提交成功');\n\n    const listAfterDelete = await sendRequest('list', null, 'Step 7 - list');\n    assertSuccess('Step 7 - list', listAfterDelete);\n    const afterDeleteList = listAfterDelete.json?.data || [];\n    const deletedPair = findPairById(afterDeleteList, addedPair.id);\n    console.log('Step 7 结果: 当前总 pair 数量 =', afterDeleteList.length);\n    if (deletedPair) {\n        throw new Error(`Step 7 - list 发现 pair 仍然存在，删除未生效: ${addedPair.id}`);\n    }\n    console.log('Step 7 校验成功: 已确认目标 pair 不在列表中');\n\n    printDivider('Pair Demo Flow 完成');\n    console.log('完整流程执行成功: list -> add -> list -> updatePair -> list -> delete -> list');\n    console.log('本次演示 pair id:', addedPair.id);\n    console.log('本次演示 pair name:', addedPair.name);\n}\n\nasync function runMessageDemoFlow() {\n    printDivider('Message Demo Flow 启动');\n    console.log(`目标接口: ${PROTOCOL}://${BASE_URL}:${PORT}${MESSAGE_API_PATH}`);\n\n    const warningText = messageDemoPayload.warning\n    printJson('Message Step 1 请求体:', buildMessagePayload('warning', warningText));\n    const warningResult = await sendMessageRequest('warning', warningText, 'Message Step 1 - warning');\n    assertSuccess('Message Step 1 - warning', warningResult);\n    console.log('Message Step 1 结果: warning 文本已提交成功');\n\n    const noticeText = messageDemoPayload.notice\n    printJson('Message Step 2 请求体:', buildMessagePayload('notice', noticeText));\n    const noticeResult = await sendMessageRequest('notice', noticeText, 'Message Step 2 - notice');\n    assertSuccess('Message Step 2 - notice', noticeResult);\n    console.log('Message Step 2 结果: notice 文本已提交成功');\n\n    printDivider('Message Demo Flow 完成');\n    console.log('完整流程执行成功: warning -> notice');\n}\n\nasync function runAllDemos() {\n    await runDemoFlow();\n    await runMessageDemoFlow();\n}\n\nif (require.main === module) {\n    runAllDemos().catch((error) => {\n        printDivider('Demo Flow 失败');\n        console.error(error?.stack || error?.message || error);\n        process.exitCode = 1;\n    });\n}\n\nmodule.exports = {\n    sendRequest,\n    sendMessageRequest,\n    runDemoFlow,\n    runMessageDemoFlow,\n    runAllDemos,\n    generateNonce,\n    pair,\n    buildSimplePayload,\n    buildMessagePayload\n};\n"
  },
  {
    "path": "socks5_install.sh",
    "content": "#!/bin/bash\n#\n# Dante SOCKS5 一键安装脚本 (Docker 版)\n# 支持: Ubuntu / Debian / CentOS / RHEL\n# 镜像: lozyme/sockd\n# 仓库: https://github.com/Lozy/danted\n#\n\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[0;33m'\nCYAN='\\033[0;36m'\nBOLD='\\033[1m'\nNC='\\033[0m'\n\nDOCKER_IMAGE=\"lozyme/sockd\"\nCONTAINER_NAME=\"sockd\"\nCONTAINER_INTERNAL_PORT=2020\nDATA_DIR=\"/opt/socks5\"\nPASSWD_FILE=\"${DATA_DIR}/sockd.passwd\"\nCONFIG_FILE=\"${DATA_DIR}/sockd.conf\"\n\nlog_info()  { echo -e \"${GREEN}[INFO]${NC} $1\"; }\nlog_warn()  { echo -e \"${YELLOW}[WARN]${NC} $1\"; }\nlog_error() { echo -e \"${RED}[ERROR]${NC} $1\"; }\ndie() { log_error \"$1\"; exit 1; }\n\nprint_banner() {\n    echo -e \"${CYAN}${BOLD}\"\n    echo \"╔══════════════════════════════════════════════╗\"\n    echo \"║   Dante SOCKS5 Proxy 一键安装 (Docker 版)   ║\"\n    echo \"║   支持: Ubuntu / Debian / CentOS            ║\"\n    echo \"╚══════════════════════════════════════════════╝\"\n    echo -e \"${NC}\"\n}\n\ncheck_root() {\n    [ \"$(id -u)\" = \"0\" ] || die \"请使用 root 用户运行此脚本\"\n}\n\ndetect_os() {\n    OS_TYPE=\"\"\n    if [ -s /etc/os-release ]; then\n        OS_NAME=$(sed -n 's/PRETTY_NAME=\"\\(.*\\)\"/\\1/p' /etc/os-release)\n        if echo \"${OS_NAME}\" | grep -Eiq 'debian|ubuntu'; then\n            OS_TYPE=\"debian\"\n        elif echo \"${OS_NAME}\" | grep -Eiq 'centos|rhel|red hat|alma|rocky|fedora|oracle'; then\n            OS_TYPE=\"centos\"\n        fi\n    fi\n    [ -n \"$OS_TYPE\" ] || die \"不支持的操作系统，仅支持 Ubuntu/Debian/CentOS\"\n    log_info \"检测到系统: ${OS_NAME}\"\n}\n\nget_default_ip() {\n    ip addr | grep 'inet ' | grep -Ev 'inet 127|inet 192\\.168|inet 10\\.|inet 172\\.' | \\\n        sed \"s/[[:space:]]*inet \\([0-9.]*\\)\\/.*/\\1/\" | head -n1\n}\n\nget_input_source() {\n    if [ -r /dev/tty ]; then\n        echo \"/dev/tty\"\n    elif [ -t 0 ]; then\n        echo \"/dev/stdin\"\n    else\n        return 1\n    fi\n}\n\nprompt_read() {\n    local var_name=\"$1\"\n    local prompt=\"$2\"\n    local input_source\n\n    input_source=$(get_input_source) || return 1\n    IFS= read -r -p \"$prompt\" \"$var_name\" < \"$input_source\"\n}\n\n# ======================== Docker 安装 ========================\n\ninstall_docker() {\n    if command -v docker &>/dev/null; then\n        log_info \"Docker 已安装: $(docker --version)\"\n        if ! systemctl is-active --quiet docker 2>/dev/null; then\n            systemctl start docker\n        fi\n        return 0\n    fi\n\n    log_info \"开始安装 Docker...\"\n\n    if command -v apt-get &>/dev/null; then\n        # Ubuntu / Debian\n        apt-get update\n        apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release\n\n        # 检测具体发行版\n        local distro\n        distro=$(. /etc/os-release && echo \"$ID\")\n\n        curl -fsSL \"https://download.docker.com/linux/${distro}/gpg\" | \\\n            gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg\n\n        echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \\\n            https://download.docker.com/linux/${distro} $(lsb_release -cs) stable\" | \\\n            tee /etc/apt/sources.list.d/docker.list > /dev/null\n\n        apt-get update\n        apt-get install -y docker-ce docker-ce-cli containerd.io\n\n    elif command -v yum &>/dev/null; then\n        # CentOS / RHEL\n        yum install -y yum-utils\n        yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo\n        yum install -y docker-ce docker-ce-cli containerd.io\n\n    elif command -v dnf &>/dev/null; then\n        # Fedora\n        dnf -y install dnf-plugins-core\n        dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo\n        dnf install -y docker-ce docker-ce-cli containerd.io\n\n    else\n        die \"不支持的包管理器，请手动安装 Docker 后重试\"\n    fi\n\n    command -v docker &>/dev/null || die \"Docker 安装失败，请手动安装后重试\"\n\n    systemctl start docker\n    systemctl enable docker\n    systemctl daemon-reload\n\n    log_info \"Docker 安装完成并已设为开机启动\"\n}\n\n# ======================== 用户配置 ========================\n\nread_user_config() {\n    local default_ip\n    default_ip=$(get_default_ip)\n    [ -z \"$default_ip\" ] && default_ip=$(ip addr | grep 'inet ' | grep -v '127.0.0' | \\\n        sed \"s/[[:space:]]*inet \\([0-9.]*\\)\\/.*/\\1/\" | head -n1)\n\n    get_input_source >/dev/null || die \"当前运行方式不支持交互输入，请改为先下载后执行，或使用 --ip/--port/--user/--passwd 参数静默安装\"\n\n    echo \"\"\n    echo -e \"${BOLD}========== 请配置 SOCKS5 代理参数 ==========${NC}\"\n    echo \"\"\n\n    prompt_read INPUT_IP \"$(echo -e \"${CYAN}[1/4]${NC} 服务器公网 IP [默认: ${default_ip}]: \")\" || die \"读取服务器公网 IP 失败\"\n    SOCKS_IP=\"${INPUT_IP:-$default_ip}\"\n\n    prompt_read INPUT_PORT \"$(echo -e \"${CYAN}[2/4]${NC} 监听端口 [默认: 2020]: \")\" || die \"读取监听端口失败\"\n    SOCKS_PORT=\"${INPUT_PORT:-2020}\"\n\n    prompt_read INPUT_USER \"$(echo -e \"${CYAN}[3/4]${NC} 认证用户名 [默认: sockd]: \")\" || die \"读取认证用户名失败\"\n    SOCKS_USER=\"${INPUT_USER:-sockd}\"\n\n    while true; do\n        prompt_read INPUT_PASS \"$(echo -e \"${CYAN}[4/4]${NC} 认证密码 (不能为空): \")\" || die \"读取认证密码失败\"\n        if [ -n \"$INPUT_PASS\" ]; then\n            SOCKS_PASS=\"$INPUT_PASS\"\n            break\n        fi\n        log_warn \"密码不能为空，请重新输入\"\n    done\n\n    echo \"\"\n    echo -e \"${BOLD}========== 确认配置信息 ==========${NC}\"\n    echo -e \"  服务器 IP: ${GREEN}${SOCKS_IP}${NC}\"\n    echo -e \"  监听端口:  ${GREEN}${SOCKS_PORT}${NC}\"\n    echo -e \"  用户名:    ${GREEN}${SOCKS_USER}${NC}\"\n    echo -e \"  密码:      ${GREEN}${SOCKS_PASS}${NC}\"\n    echo \"\"\n    prompt_read CONFIRM \"确认以上配置开始安装? [Y/n]: \" || die \"读取确认选项失败\"\n    CONFIRM=\"${CONFIRM:-Y}\"\n    if [[ ! \"$CONFIRM\" =~ ^[Yy]$ ]]; then\n        log_info \"已取消安装\"\n        exit 0\n    fi\n}\n\nparse_args() {\n    for param in \"$@\"; do\n        case \"$param\" in\n            --ip=*)       SOCKS_IP=\"${param#--ip=}\" ;;\n            --port=*)     SOCKS_PORT=\"${param#--port=}\" ;;\n            --user=*)     SOCKS_USER=\"${param#--user=}\" ;;\n            --passwd=*)   SOCKS_PASS=\"${param#--passwd=}\" ;;\n            --uninstall)  do_uninstall; exit 0 ;;\n            --adduser)    ACTION=\"adduser\" ;;\n            --deluser)    ACTION=\"deluser\" ;;\n            --showuser)   ACTION=\"showuser\" ;;\n            --help|-h)    show_help; exit 0 ;;\n        esac\n    done\n}\n\nshow_help() {\n    echo \"用法: bash $0 [选项]\"\n    echo \"\"\n    echo \"安装:\"\n    echo \"  bash $0                                         交互式安装\"\n    echo \"  bash $0 --ip=IP --port=PORT --user=U --passwd=P 静默安装\"\n    echo \"\"\n    echo \"管理:\"\n    echo \"  bash $0 --adduser --user=NAME --passwd=PASS     添加用户\"\n    echo \"  bash $0 --deluser --user=NAME                   删除用户\"\n    echo \"  bash $0 --showuser                              查看用户列表\"\n    echo \"  bash $0 --uninstall                             卸载\"\n    echo \"  bash $0 --help                                  帮助\"\n}\n\n# ======================== 用户管理 ========================\n\ndo_adduser() {\n    local user=\"$1\"\n    local pass=\"$2\"\n    [ -z \"$user\" ] || [ -z \"$pass\" ] && die \"用户名和密码不能为空\"\n\n    docker exec \"${CONTAINER_NAME}\" script/pam add \"$user\" \"$pass\" || \\\n        die \"添加用户失败\"\n    log_info \"用户 ${user} 添加成功\"\n}\n\ndo_deluser() {\n    local user=\"$1\"\n    [ -z \"$user\" ] && die \"用户名不能为空\"\n\n    docker exec \"${CONTAINER_NAME}\" script/pam del \"$user\" || \\\n        die \"删除用户失败\"\n    log_info \"用户 ${user} 删除成功\"\n}\n\ndo_showuser() {\n    echo -e \"${BOLD}当前 SOCKS5 用户列表:${NC}\"\n    docker exec \"${CONTAINER_NAME}\" script/pam show\n}\n\n# ======================== 卸载 ========================\n\ndo_uninstall() {\n    log_info \"正在卸载 SOCKS5 服务...\"\n    docker stop \"${CONTAINER_NAME}\" 2>/dev/null || true\n    docker rm \"${CONTAINER_NAME}\" 2>/dev/null || true\n\n    prompt_read DEL_DATA \"是否删除配置和数据目录 ${DATA_DIR}? [y/N]: \" || DEL_DATA=\"N\"\n    if [[ \"$DEL_DATA\" =~ ^[Yy]$ ]]; then\n        rm -rf \"${DATA_DIR}\"\n        log_info \"数据目录已删除\"\n    fi\n\n    prompt_read DEL_IMG \"是否删除 Docker 镜像 ${DOCKER_IMAGE}? [y/N]: \" || DEL_IMG=\"N\"\n    if [[ \"$DEL_IMG\" =~ ^[Yy]$ ]]; then\n        docker rmi \"${DOCKER_IMAGE}\" 2>/dev/null || true\n        log_info \"Docker 镜像已删除\"\n    fi\n\n    log_info \"卸载完成\"\n}\n\n# ======================== 防火墙 ========================\n\nsetup_firewall() {\n    if command -v firewall-cmd &>/dev/null && systemctl is-active --quiet firewalld 2>/dev/null; then\n        firewall-cmd --permanent --add-port=\"${SOCKS_PORT}\"/tcp 2>/dev/null\n        firewall-cmd --reload 2>/dev/null\n        log_info \"firewalld 已放行端口 ${SOCKS_PORT}\"\n    elif command -v ufw &>/dev/null; then\n        ufw allow \"${SOCKS_PORT}\"/tcp 2>/dev/null\n        log_info \"UFW 已放行端口 ${SOCKS_PORT}\"\n    fi\n\n    if command -v iptables &>/dev/null; then\n        if ! iptables -C INPUT -p tcp --dport \"${SOCKS_PORT}\" -j ACCEPT 2>/dev/null; then\n            iptables -I INPUT -p tcp --dport \"${SOCKS_PORT}\" -j ACCEPT 2>/dev/null\n            log_info \"iptables 已放行端口 ${SOCKS_PORT}\"\n            if [ -f /etc/sysconfig/iptables ]; then\n                service iptables save 2>/dev/null || iptables-save > /etc/sysconfig/iptables 2>/dev/null || true\n            fi\n        fi\n    fi\n}\n\n# ======================== 部署 ========================\n\ndeploy_container() {\n    mkdir -p \"${DATA_DIR}\"\n\n    # 生成自定义配置文件\n    log_info \"生成配置文件...\"\n    cat > \"${CONFIG_FILE}\" << 'CFGEOF'\ninternal: 0.0.0.0 port = 2020\nexternal: eth0\n\nmethod: pam none\nclientmethod: none\n\nuser.privileged: root\nuser.notprivileged: nobody\n\nlogoutput: stdout\n\nclient pass {\n    from: 0.0.0.0/0 to: 0.0.0.0/0\n}\n\nclient block {\n    from: 0.0.0.0/0 to: 0.0.0.0/0\n}\n\npass {\n    from: 0.0.0.0/0 to: 0.0.0.0/0\n    protocol: tcp udp\n    method: pam\n    log: connect disconnect\n}\n\nblock {\n    from: 0.0.0.0/0 to: 0.0.0.0/0\n    log: connect error\n}\nCFGEOF\n\n    # 生成空密码文件 (后面通过 docker exec 添加用户)\n    touch \"${PASSWD_FILE}\"\n\n    # 移除旧容器\n    docker stop \"${CONTAINER_NAME}\" 2>/dev/null || true\n    docker rm \"${CONTAINER_NAME}\" 2>/dev/null || true\n\n    # 拉取镜像\n    log_info \"拉取 Docker 镜像 ${DOCKER_IMAGE}...\"\n    docker pull \"${DOCKER_IMAGE}\" || die \"拉取镜像失败，请检查网络\"\n\n    # 启动容器\n    log_info \"启动容器...\"\n    docker run -d \\\n        --name \"${CONTAINER_NAME}\" \\\n        --restart=always \\\n        --publish \"${SOCKS_PORT}:${CONTAINER_INTERNAL_PORT}\" \\\n        --volume \"${PASSWD_FILE}:/home/danted/conf/sockd.passwd\" \\\n        --volume \"${CONFIG_FILE}:/home/danted/conf/sockd.conf\" \\\n        \"${DOCKER_IMAGE}\" || die \"启动容器失败\"\n\n    sleep 2\n\n    if docker ps --format '{{.Names}}' | grep -q \"^${CONTAINER_NAME}$\"; then\n        log_info \"容器启动成功\"\n    else\n        log_error \"容器启动失败，查看日志:\"\n        docker logs \"${CONTAINER_NAME}\" 2>&1 | tail -20\n        die \"容器启动异常\"\n    fi\n\n    # 添加用户\n    log_info \"添加认证用户 ${SOCKS_USER}...\"\n    docker exec \"${CONTAINER_NAME}\" script/pam add \"${SOCKS_USER}\" \"${SOCKS_PASS}\" || \\\n        die \"添加用户失败\"\n\n    log_info \"部署完成\"\n}\n\n# ======================== 结果展示 ========================\n\nshow_result() {\n    local status\n    if docker ps --format '{{.Names}}' | grep -q \"^${CONTAINER_NAME}$\"; then\n        status=\"${GREEN}● 运行中${NC}\"\n    else\n        status=\"${RED}● 未运行${NC}\"\n    fi\n\n    echo \"\"\n    echo -e \"${CYAN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}\"\n    echo -e \"${CYAN}${BOLD}║              SOCKS5 代理服务 安装完成!                      ║${NC}\"\n    echo -e \"${CYAN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}\"\n    echo \"\"\n    echo -e \"  ${BOLD}服务状态:${NC}  $status\"\n    echo -e \"  ${BOLD}服务地址:${NC}  ${GREEN}${SOCKS_IP}${NC}\"\n    echo -e \"  ${BOLD}服务端口:${NC}  ${GREEN}${SOCKS_PORT}${NC}\"\n    echo -e \"  ${BOLD}用户名  :${NC}  ${GREEN}${SOCKS_USER}${NC}\"\n    echo -e \"  ${BOLD}密码    :${NC}  ${GREEN}${SOCKS_PASS}${NC}\"\n    echo -e \"  ${BOLD}协议    :${NC}  SOCKS5\"\n    echo -e \"  ${BOLD}开机启动:${NC}  ${GREEN}已启用 (Docker restart=always)${NC}\"\n    echo \"\"\n    echo -e \"  ${BOLD}配置文件:${NC}  ${CONFIG_FILE}\"\n    echo -e \"  ${BOLD}密码文件:${NC}  ${PASSWD_FILE}\"\n    echo \"\"\n    echo -e \"  ${CYAN}${BOLD}服务管理:${NC}\"\n    echo -e \"    启动:   ${YELLOW}docker start ${CONTAINER_NAME}${NC}\"\n    echo -e \"    停止:   ${YELLOW}docker stop ${CONTAINER_NAME}${NC}\"\n    echo -e \"    重启:   ${YELLOW}docker restart ${CONTAINER_NAME}${NC}\"\n    echo -e \"    日志:   ${YELLOW}docker logs -f ${CONTAINER_NAME}${NC}\"\n    echo \"\"\n    echo -e \"  ${CYAN}${BOLD}用户管理:${NC}\"\n    echo -e \"    查看用户:  ${YELLOW}docker exec ${CONTAINER_NAME} script/pam show${NC}\"\n    echo -e \"    添加用户:  ${YELLOW}docker exec ${CONTAINER_NAME} script/pam add 用户名 密码${NC}\"\n    echo -e \"    删除用户:  ${YELLOW}docker exec ${CONTAINER_NAME} script/pam del 用户名${NC}\"\n    echo \"\"\n    echo -e \"  ${CYAN}${BOLD}测试连接:${NC}\"\n    echo -e \"    ${YELLOW}curl -x socks5h://${SOCKS_USER}:${SOCKS_PASS}@${SOCKS_IP}:${SOCKS_PORT} https://ifconfig.co${NC}\"\n    echo \"\"\n    echo -e \"  ${CYAN}${BOLD}卸载:${NC}\"\n    echo -e \"    ${YELLOW}bash $0 --uninstall${NC}\"\n    echo \"\"\n}\n\n# ======================== 主流程 ========================\n\nmain() {\n    print_banner\n    check_root\n    detect_os\n\n    parse_args \"$@\"\n\n    # 用户管理子命令\n    case \"${ACTION}\" in\n        adduser)\n            [ -z \"$SOCKS_USER\" ] || [ -z \"$SOCKS_PASS\" ] && die \"请指定 --user= 和 --passwd=\"\n            do_adduser \"$SOCKS_USER\" \"$SOCKS_PASS\"\n            exit 0 ;;\n        deluser)\n            [ -z \"$SOCKS_USER\" ] && die \"请指定 --user=\"\n            do_deluser \"$SOCKS_USER\"\n            exit 0 ;;\n        showuser)\n            do_showuser\n            exit 0 ;;\n    esac\n\n    # 安装流程\n    if [ -z \"$SOCKS_PASS\" ]; then\n        read_user_config\n    else\n        SOCKS_IP=\"${SOCKS_IP:-$(get_default_ip)}\"\n        SOCKS_PORT=\"${SOCKS_PORT:-2020}\"\n        SOCKS_USER=\"${SOCKS_USER:-sockd}\"\n    fi\n\n    install_docker\n    setup_firewall\n    deploy_container\n    show_result\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "ubuntu-x64-install.sh",
    "content": "#!/bin/bash\n\n# This script is for installing Astro on Ubuntu Server\n# System Requirements:\n# - Ubuntu Server 24 LTS or higher\n# - At least 1GB RAM\n# - At least 10GB free disk space\n# - Root privileges required\n\n# Exit on error to ensure script stops if any command fails\nset -e\n\n# 设置英文环境确保命令输出一致\nexport LANG=C\n\n# 系统要求检测函数\ncheck_system() {\n    echo \"正在获取系统信息...\"\n    local host_output\n    host_output=$(hostnamectl 2>&1)\n    \n    # 打印完整系统信息\n    echo -e \"\\n\\033[36m=== 系统信息检测结果 ===\\033[0m\"\n    echo \"$host_output\"\n    echo -e \"\\033[36m========================\\033[0m\\n\"\n    \n    # 检测操作系统\n    if ! grep -qP \"Operating System:\\s+Ubuntu 24(\\.\\d+)+ LTS\" <<< \"$host_output\"; then\n        echo -e \"\\033[31m✗ 错误：必须使用 Ubuntu 24.x 系统\\033[0m\"\n        return 1\n    fi\n    \n    # 检测系统架构\n    if ! grep -q \"Architecture: x86-64\" <<< \"$host_output\"; then\n        echo -e \"\\033[31m✗ 错误：必须使用 x86-64 架构\\033[0m\"\n        return 1\n    fi\n    \n    echo -e \"\\033[32m✓ 系统检测通过：Ubuntu 24.x / x86-64\\033[0m\"\n    return 0\n}\n\n# 执行系统检测\necho -e \"\\n开始系统环境验证...\"\nif ! check_system; then\n    echo -e \"\\n\\033[41m系统环境不满足要求，脚本终止执行\\033[0m\"\n    exit 1\nfi\n\n# Function to check and install required tools\ncheck_and_install_tools() {\n    echo \"----> [ASTRO-INSTALL] Checking required tools...\"\n    local tools=(\"curl\" \"wget\" \"unzip\" \"apt-get\")\n    local missing_tools=()\n\n    # Check which tools are missing\n    for tool in \"${tools[@]}\"; do\n        if ! command -v \"$tool\" &> /dev/null; then\n            missing_tools+=(\"$tool\")\n        fi\n    done\n\n    # Install missing tools if any\n    if [ ${#missing_tools[@]} -ne 0 ]; then\n        echo \"----> [ASTRO-INSTALL] Installing missing tools: ${missing_tools[*]}\"\n        apt-get update\n        apt-get install -y \"${missing_tools[@]}\"\n    fi\n}\n\n# Function to validate IP address\nvalidate_ip() {\n    local ip=$1\n    \n    # Check if empty\n    if [ -z \"$ip\" ]; then\n        return 1\n    fi\n    \n    # Check basic format: should have exactly 3 dots\n    if [ \"$(echo \"$ip\" | tr -cd '.' | wc -c)\" -ne 3 ]; then\n        return 1\n    fi\n    \n    # Split IP into parts and validate each part\n    IFS='.' read -r part1 part2 part3 part4 <<< \"$ip\"\n    \n    # Check each part is a number between 0-255\n    for part in \"$part1\" \"$part2\" \"$part3\" \"$part4\"; do\n        # Check if part is numeric\n        if ! [[ \"$part\" =~ ^[0-9]+$ ]]; then\n            return 1\n        fi\n        # Check range 0-255\n        if [ \"$part\" -lt 0 ] || [ \"$part\" -gt 255 ]; then\n            return 1\n        fi\n        # Check no leading zeros (except for \"0\")\n        if [ \"${#part}\" -gt 1 ] && [ \"${part:0:1}\" = \"0\" ]; then\n            return 1\n        fi\n    done\n    \n    return 0\n}\n\n# 可靠的IP获取函数\nget_server_ip() {\n    # 尝试自动获取公网IP\n    auto_ip=$(curl -s https://api.ipify.org || true)\n    \n    if validate_ip \"$auto_ip\"; then\n        echo \"----> [ASTRO-INSTALL] Detected public IP: $auto_ip\" > /dev/tty\n        \n        # 询问是否使用自动获取的IP\n        read -p $'\\n----> [ASTRO-INSTALL] Use this IP? [Y/n] ' confirm < /dev/tty\n        if [[ -z \"$confirm\" || \"$confirm\" =~ ^[Yy] ]]; then\n            SERVER_IP=\"$auto_ip\"\n            return\n        fi\n    fi\n    \n    # 手动输入\n    while true; do\n        echo -e \"\\n----> [ASTRO-INSTALL] Please enter your server's public IP address\" > /dev/tty\n        read -p \"IP: \" SERVER_IP < /dev/tty\n        \n        if validate_ip \"$SERVER_IP\"; then\n            break\n        else\n            echo \"ERROR: Invalid IP format (e.g. 192.168.1.1)\" > /dev/tty\n        fi\n    done\n}\n\necho \"----> [ASTRO-INSTALL] Starting Astro installation...\" > /dev/tty\n\n# === 主流程 ===\nget_server_ip\n\n# Check if running with root privileges\n# This script needs to be run with sudo on Ubuntu Server\nif [ \"$EUID\" -ne 0 ]; then \n    echo \"----> [ASTRO-INSTALL] ERROR: Please run this script with sudo\"\n    exit 1\nfi\n\n# Check and install required tools\ncheck_and_install_tools\n\n# 1. Install Node.js 23.x\n# Using NodeSource repository to install the latest Node.js 23.x version\necho \"----> [ASTRO-INSTALL] Installing Node.js 23.x...\"\ncurl -sL https://deb.nodesource.com/setup_23.x | bash -\nsudo apt-get install --allow-downgrades -y nodejs=23.11.1-1nodesource1\n\n# Verify Node.js installation\nnode_version=$(node -v)\necho \"----> [ASTRO-INSTALL] Node.js version: $node_version\"\n\n# 2. Install global dependencies\n# Installing required tools for running Astro:\n# - pm2: for process management and daemon\n# - bytenode: for JavaScript compilation\n# - yarn: package manager\necho \"----> [ASTRO-INSTALL] Installing global dependencies...\"\nnpm install -g pm2 bytenode yarn\necho \"----> [ASTRO-INSTALL] Installing pm2-logrotate...\"\npm2 install pm2-logrotate\n\n# 3. Download and extract latest version\n# Using GitHub API to get the latest release download link\necho \"----> [ASTRO-INSTALL] Downloading latest version...\"\nLATEST_RELEASE_URL=$(curl -s https://api.github.com/repos/astro-btc/astro/releases/latest | grep \"browser_download_url.*zip\" | cut -d '\"' -f 4)\nRELEASE_FILENAME=$(basename \"$LATEST_RELEASE_URL\")\n\n# Download the file\nwget \"$LATEST_RELEASE_URL\"\n\n# Extract files to current directory\necho \"----> [ASTRO-INSTALL] Extracting files...\"\nunzip \"$RELEASE_FILENAME\"\n\n# Fix permissions for extracted directories\necho \"----> [ASTRO-INSTALL] Fixing file permissions...\"\nchmod -R 755 astro-core astro-server astro-admin 2>/dev/null || true\nchown -R $SUDO_USER:$SUDO_USER astro-core astro-server astro-admin 2>/dev/null || true\n\n# Clean up downloaded zip file to save space\nrm \"$RELEASE_FILENAME\"\n\n# 4. Setup astro-core\necho \"----> [ASTRO-INSTALL] Setting up astro-core...\"\n\n# Enter project directory\ncd astro-core || exit 1\n\n# Install dependencies excluding better-sqlite3\necho \"----> [ASTRO-INSTALL] Installing astro-core dependencies (excluding better-sqlite3)...\"\nyarn install --ignore-scripts\n\n# Download and install precompiled better-sqlite3\necho \"----> [ASTRO-INSTALL] Downloading precompiled better-sqlite3...\"\ncd node_modules || exit 1\n\n# Download the precompiled package\nwget -O bs3-ubuntu-x64.gz \"https://raw.githubusercontent.com/astro-btc/astro/refs/heads/main/bs3-ubuntu-x64.gz\"\n\n# Extract to better-sqlite3 directory\necho \"----> [ASTRO-INSTALL] Extracting better-sqlite3...\"\nmkdir -p better-sqlite3\ntar -xzf bs3-ubuntu-x64.gz\n\n# Clean up downloaded file\nrm bs3-ubuntu-x64.gz\n\n# Return to astro-core directory\ncd ..\npm2 start pm2.config.js\necho \"----> [ASTRO-INSTALL] astro-core setup completed\"\n\n# 5. Configure astro-server\necho \"----> [ASTRO-INSTALL] Configuring astro-server...\"\ncd ../astro-server || exit 1\n\n# Update .env file with the IP address\nif [ -f .env ]; then\n    sed -i \"s/ALLOWED_DOMAIN=.*/ALLOWED_DOMAIN=$SERVER_IP/\" .env\n    echo \"----> [ASTRO-INSTALL] Updated .env file with IP: $SERVER_IP\"\nelse\n    echo \"----> [ASTRO-INSTALL] ERROR: .env file not found in astro-server directory\"\n    exit 1\nfi\n\n# Install dependencies and start server\necho \"----> [ASTRO-INSTALL] Installing astro-server dependencies and starting service...\"\nyarn\npm2 start pm2.config.js\n\n# 6. Setup PM2 startup\necho \"----> [ASTRO-INSTALL] Setting up PM2 startup...\"\npm2 startup\npm2 save\n\necho \"----> [ASTRO-INSTALL] Installation completed!\"\necho \"----> [ASTRO-INSTALL] 打开浏览器访问: https://$SERVER_IP:12345/change-after-install/\"\necho \"----> [ASTRO-INSTALL] 默认密码：Astro321@\"\necho \"----> [ASTRO-INSTALL] 默认Google二次认证码：GRY5ZVAXTSYZFXFUSP7BH5QEYTEMZU42 （需要手动导入Google Authenticator）\"\n"
  }
]