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,则说明端口已成功开放了 \

#### 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)

#### b. Bybit
保证金模式设置为: **全仓保证金** \
权限相关参考下图:\

#### c. Bitget
支持统一账户 & 经典账户 \
请使用联合保证金模式, 并开启双向持仓,权限相关参考下图:\

#### d. OKX
请使用 「合约模式】 或 「跨币种保证金模式」,并开启双向持仓
权限相关参考下图:\

#### e. Gate
==> 请开启双向持仓(交易设置->交易配置->持仓模式选择双向持仓) \
==> 请使用 **统一账户 + 跨币种保证金模式**,权限相关参考下图:\

#### f. Kucoin
==> 请开启双向持仓 \
权限相关参考下图:\

#### g. Aster
联合保证金模式 + 双向持仓
#### h. Backpack🎒
https://backpack.exchange/portfolio/settings/api-keys
#### i.Hyperliquid
需要三个数据,主钱包地址,代理钱包地址,代理钱包私钥,请参考下面两张图:\
 \

#### j. Htx
==> 请设置资产模式为联合保证金模式(资产模式->联合保证金模式)\
==> 请设置为双向持仓 (持仓模式->双向持仓)
权限相关参考下图:\
 \

#### k. Lighter
1. 切勿勾选「仅减仓」选项, 否则无法开单 \
API KEY 相关参考下图:\
 \

================================================
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' </dev/urandom | head -c "$length"
}
# 生成Google Authenticator密钥(Base32,基于 /dev/urandom)
generate_2fa_secret() {
LC_ALL=C tr -dc 'A-Z2-7' </dev/urandom | head -c 32
}
# 生成管理员登录密码(前7位随机字母数字 + 末位 @)
generate_security_code() {
local prefix
prefix=$(LC_ALL=C tr -dc 'A-Za-z0-9' </dev/urandom | head -c 7)
echo "${prefix}@"
}
# 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
# 解析 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)"
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
SYMBOL INDEX (26 symbols across 1 files)
FILE: sdk-demo.js
constant ACTIONS_WITH_PAIR (line 5) | const ACTIONS_WITH_PAIR = new Set(['add', 'update', 'delete'])
constant MESSAGE_TYPES (line 6) | const MESSAGE_TYPES = new Set(['warning', 'notice'])
function generateNonce (line 8) | function generateNonce() {
function sleep (line 16) | function sleep(ms) {
constant API_KEY (line 55) | const API_KEY = process.env.ASTRO_API_KEY || '***';
constant BASE_URL (line 58) | const BASE_URL = '127.0.0.1';
constant PORT (line 59) | const PORT = 8443;
constant PAIR_API_PATH (line 60) | const PAIR_API_PATH = '/api/config/sdk-update-pair';
constant MESSAGE_API_PATH (line 61) | const MESSAGE_API_PATH = '/api/config/sdk-send-message';
constant USE_HTTPS (line 64) | const USE_HTTPS = true;
constant PROTOCOL (line 65) | const PROTOCOL = USE_HTTPS ? 'https' : 'http';
constant HTTP_MODULE (line 66) | const HTTP_MODULE = USE_HTTPS ? https : http;
function createUpdatedPair (line 96) | function createUpdatedPair(addedPair) {
function printDivider (line 109) | function printDivider(title) {
function printJson (line 115) | function printJson(title, value) {
function summarizePair (line 120) | function summarizePair(targetPair) {
function assertSuccess (line 140) | function assertSuccess(stepName, response) {
function findPairByName (line 146) | function findPairByName(list, name) {
function findPairById (line 150) | function findPairById(list, id) {
function buildLogSafeHeaders (line 154) | function buildLogSafeHeaders(headers) {
function sendSignedRequest (line 162) | function sendSignedRequest(apiPath, payload, stepLabel, summary = {}) {
function sendRequest (line 249) | function sendRequest(action, pairData, stepLabel) {
function sendMessageRequest (line 254) | function sendMessageRequest(type, text, stepLabel) {
function runDemoFlow (line 266) | async function runDemoFlow() {
function runMessageDemoFlow (line 335) | async function runMessageDemoFlow() {
function runAllDemos (line 355) | async function runAllDemos() {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (102K chars).
[
{
"path": "Docs/GRID.md",
"chars": 1511,
"preview": "## 汇率网格交易产品说明文档\n\n### 初始化汇率网格必填参数,举例ETH/BTC汇率交易对\n#### 需用户设置的参数:(也就是说开启网格功能只需设置格子数量参数即可)\n1. 最小汇率值 - 0.025 // 即网格下限, 对应我"
},
{
"path": "Docs/STEP.md",
"chars": 1836,
"preview": "# 梯度开清示例\n\n### 1. 对于 SF(现期), FF(期期)模式\n示例 (可以直接复制或编辑后复制, 在Astro开单页面导入即可):\n\n```\nASTRO-QUICK-COPY:\n{\n \"type\": \"SF\",\n \"name"
},
{
"path": "Docs/常见问题.md",
"chars": 530,
"preview": "### ----- 如果查阅完此文档, 问题依然没有解决, 请TG私信群主寻求帮助 -----\n\n#### 1. 为什么安装完成后打不开页面?\n请使用最新版本Chrome浏览器 \\\n请复制HTTPS链接地址,不要手动输入, 注意是http"
},
{
"path": "INSTALL.md",
"chars": 2567,
"preview": "### [Astro产品介绍](./README.md) \n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECUR"
},
{
"path": "README.md",
"chars": 174,
"preview": "\n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECURITY.md) \n\n请仔细阅读产品文档: https:/"
},
{
"path": "SDK-API.md",
"chars": 7810,
"preview": "# Astro SDK 接口文档\n\n本文档主要说明 `ASTRO SDK API` 相关接口的调用方式。 代码参考 [demo](./sdk-demo.js)\n\n## 0. 设置API KEY\n// API Key - 需要先在账户->开发"
},
{
"path": "SECURITY.md",
"chars": 513,
"preview": "### [Astro产品介绍](./README.md) \n### [Astro安装教程](./INSTALL.md) \n### [Astro常见问题](./Docs/常见问题.md) \n### [Astro安全相关-必读](./SECUR"
},
{
"path": "SOCKS5_INSTALL.md",
"chars": 604,
"preview": "### SOCKS5服务 一键安装教程\n\n在ubuntu 24 或 centos 7 系统执行以下命令\n\n```\ncurl -L https://raw.githubusercontent.com/astro-btc/Astro/refs/"
},
{
"path": "install-in-docker.sh",
"chars": 4222,
"preview": "#!/bin/bash\n\n# This script is for installing Astro in docker\n# apt-get update && apt-get install -y curl\n# cd /home/ubun"
},
{
"path": "install-with-docker.sh",
"chars": 13245,
"preview": "#!/bin/bash\n\n# Astro 一键安装脚本\n# 支持的系统: 主流Linux发行版\n\nset -e\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\n"
},
{
"path": "install.sh",
"chars": 14940,
"preview": "#!/bin/bash\n\n# Astro 一键安装脚本\n# 支持的系统: 主流Linux发行版\n\nset -e\n\n# 颜色定义\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\n"
},
{
"path": "sdk-demo.js",
"chars": 12088,
"preview": "const crypto = require('crypto')\nconst http = require('http');\nconst https = require('https');\n\nconst ACTIONS_WITH_PAIR "
},
{
"path": "socks5_install.sh",
"chars": 13395,
"preview": "#!/bin/bash\n#\n# Dante SOCKS5 一键安装脚本 (Docker 版)\n# 支持: Ubuntu / Debian / CentOS / RHEL\n# 镜像: lozyme/sockd\n# 仓库: https://gi"
},
{
"path": "ubuntu-x64-install.sh",
"chars": 7340,
"preview": "#!/bin/bash\n\n# This script is for installing Astro on Ubuntu Server\n# System Requirements:\n# - Ubuntu Server 24 LTS or h"
}
]
About this extraction
This page contains the full source code of the astro-btc/Astro GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (78.9 KB), approximately 26.6k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.