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